async.md
5.32 KB
- 在JavaScript的世界中,所有代码都 是单线程执行的
- 由于这个“缺陷”,导致 JavaScript 的所有网络操作,浏览器事件,都必须是异步执行。异步执行可以用回调函数实现
- 异步操作会在将来的某个时间点触发一个函数调用
- 主流的异步处理方案主要有:回调函数 (CallBack) 、 Promise 、 Generator 函数、 async/await。
回调函数
这是异步编程最基本、最直观的方式
downloadAsync("http://example.com/file.txt", (text) => {
console.log(text);
});
Promise
解决回调嵌套层数过多的问题
downloadAsync("a.txt", (a) => {
downloadAsync("b.txt", (b) => {
downloadAsync("c.txt", (c) => {
console.log("Contents: " + a + b + c);
}, function(error) {
console.log("Error: " + error);
});
}, function(error) {
console.log("Error: " + error);
});
}, function(error) {
console.log("Error: " + error);
});
downloadAsync("a.txt").then(() => {
return downloadAsync("b.txt");
}).then((b) => {
return downloadAsync("c.txt");
}).then((c) => {
console.log("Contents: " + a + b + c);
}).catch((error) => {
console.log("Error: " + error);
});
var myPromise = new Promise((resolve, reject) => {
... //异步操作
if( success ) {
resolve(value);
} else {
reject(error);
}
});
var myPromise = new Promise((resolve, reject) => {
resolve(1);
});
myPromise.then((value) => {
console.log("第" + value + "一次异步操作成功"); //第1次异步操作成功
return value+1;
}).then(function(value) {
console.log("第" + value + "一次异步操作成功"); //第2次异步操作成功
});
Generator
Promise异步编程,可以很好地回避回调地狱。但Promise的问题是,不管什么样的异步操作,被Promise一包装,看上去都是一堆then,语义方面还不够清晰。因此更好的异步编程解决方案是ES6的Generator。
function* gen() {
yield 1;
yield 2;
yield 3;
}
var g = gen();
console.log(g); // Generator {}
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: 3, done: true }
console.log(g.next()); // { value: undefined, done: true }
调用Generator只会得到一个遍历器对象,仅此而已。并不会执行Generator函数。上例中var g = gen();,变量g是一个遍历器对象,即一个指向内部状态的指针对象,用于之后遍历yield定义的内部状态。
有了遍历器对象g之后,就可以使用next方法使指针依次移向下一个状态,即让函数从开头或上一次暂停的地方开始执行,执行到下一个yield或return语句为止。虽然yeild和next本质上是遍历器对象和操作指针,但你使用时可以将它们简单地理解为:
Generator是分段执行的函数。yeild是暂停的标记。next用于继续执行。
自动遍历
function* numbers () {
yield 1;
yield 2;
return 3;
yield 4;
}
//for...of
var gen1 = numbers();
for (let n of gen1) {
console.log(n); //1 2
}
//Array.from
console.log(Array.from(numbers())); // [1, 2]
//扩展运算符(…)
console.log([...numbers()]); // [1, 2]
//解构赋值
let [x, y] = numbers();
console.log(x); //1
console.log(y); //2
应用
function delay(time, callback){
setTimeout(function(){
callback("sleep "+time);
},time);
}
delay(1000,function(msg){
console.log(msg);
delay(2000,function(msg){
console.log(msg);
});
});
//1秒后打印出:sleep 1000
//再过2秒打印出:sleep 2000
function delay(time, callback){
setTimeout(function(){
callback("sleep "+time);
},time);
}
function run(genFunc) {
var g = genFunc(resume);
function resume(value) {
g.next(value);
}
g.next();
}
run(function* delayedMsg(resume) {
console.log(yield delay(1000, resume));
console.log(yield delay(2000, resume));
});
//1秒后打印出:sleep 1000
//再过2秒打印出:sleep 2000
async/await
async函数就是Generator函数的语法糖,使得异步操作的流程更加清晰。
function delay(time){
return new Promise(function(resolve) {
setTimeout(function(){
resolve("sleep "+time);
},time);
});
}
async function run() {
console.log(await delay(1000));
console.log(await delay(2000));
}
async函数的await命令后面是Promise对象(如果是原始类型的值,会自动将其转成Promise对象并立即将状态设成Resolved,效果等于同步操作)。进一步说,async函数完全可以看作多个异步操作,包装成的一个Promise对象,而await命令就是内部then命令的语法糖。因为await命令后面是Promise对象,需要考虑rejected的情况,毕竟谁也不能断言异步操作中不会出现异常,所以最好把await命令包进try…catch中:
async function myAsyncFun() {
try {
await somePromise();
} catch (err) {
console.log(err);
}
}
//另一种写法
async function myAsyncFun() {
await somePromise().catch(function (err) {console.log(err);});
}