1. 含义
async
函数是什么?一句话,它就是 Generator 函数的语法糖。
async
函数就是将 Generator 函数的星号(*)替换成async
,将yield
替换成await
,仅此而已。
async
函数对 Generator 函数的改进,体现在以下四点
- (1)内置执行器。
Generator 函数的执行必须靠执行器,所以才有了co模块,而async函数自带执行器。也就是说,async函数的执行,与普通函数一模一样,只要一行。 - (2)更好的语义。
async
和await
,比起星号和yield
,语义更清楚了。async
表示函数里有异步操作,await
表示紧跟在后面的表达式需要等待结果。 - (3)更广的适用性。
co
模块约定,yield
命令后面只能是 Thunk 函数或 Promise 对象,而async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时等同于同步操作)。 - (4)返回值是 Promise。
async
函数的返回值是 Promise 对象,这比 Generator 函数的返回值是Iterator
对象方便多了。你可以用then
方法指定下一步的操作。
进一步说,async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖。
2. 基本用法
async
函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await
就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
async function getStockPriceByName(name) {
const symbol = await getStockSymbol(name);
const stockPrice = await getStockPrice(symbol);
return stockPrice;
}
getStockPriceByName('goog').then(function (result) {
console.log(result);
});
3. 语法
async
函数的语法规则总体上比较简单,难点是错误处理机制。
- 返回 Promise 对象
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
async
函数内部抛出错误,会导致返回的 Promise 对象变为reject
状态。抛出的错误对象会被catch
方法回调函数接收到。 - Promise 对象的状态变化
async
函数返回的 Promise 对象,必须等到内部所有await
命令后面的Promise
对象执行完,才会发生状态改变,除非遇到return
语句或者抛出错误。也就是说,只有async
函数内部的异步操作执行完,才会执行then
方法指定的回调函数。 - await 命令
正常情况下,await
命令后面是一个 Promise 对象。如果不是,会被转成一个立即resolve
的 Promise 对象。
await
命令后面的 Promise 对象如果变为reject
状态,则reject的参数会被catch
方法的回调函数接收到。
只要一个await
语句后面的 Promise 变为reject
,那么整个async
函数都会中断执行。
有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个await
放在try...catch
结构里面,这样不管这个异步操作是否成功,第二个await
都会执行。 - 错误处理
如果await
后面的异步操作出错,那么等同于async
函数返回的 Promise 对象被reject
。 - 使用注意点
第一点,前面已经说过,await
命令后面的Promise对象,运行结果可能是rejected
,所以最好把await
命令放在try...catch
代码块中。
第二点,多个await
命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
第三点,await
命令只能用在async
函数之中,如果用在普通函数,就会报错
4. async 函数的实现原理
async
函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
5.异步遍历器
Iterator 接口是一种数据遍历的协议,只要调用遍历器对象的next
方法,就会得到一个对象,表示当前遍历指针所在的那个位置的信息。next
方法返回的对象的结构是{value, done}
,其中value
表示当前的数据的值,done
是一个布尔值,表示遍历是否结束。
这里隐含着一个规定,next
方法必须是同步的,只要调用就必须立刻返回值。也就是说,一旦执行next
方法,就必须同步地得到value
和done
这两个属性。如果遍历指针正好指向同步操作,当然没有问题,但对于异步操作,就不太合适了。目前的解决方法是,Generator 函数里面的异步操作,返回一个 Thunk 函数或者 Promise 对象,即value
属性是一个 Thunk 函数或者 Promise 对象,等待以后返回真正的值,而done
属性则还是同步产生的。
ES2018 引入了”异步遍历器“(Async Iterator),为异步操作提供原生的遍历器接口,即value
和done
这两个属性都是异步产生。
- 异步遍历器接口
异步遍历器的最大的语法特点,就是调用遍历器的next
方法,返回的是一个 Promise 对象。 - for await...of
前面介绍过,for...of
循环用于遍历同步的 Iterator 接口。新引入的for await...of
循环,则是用于遍历异步的 Iterator 接口。
async function f() {
for await (const x of createAsyncIterable(['a', 'b'])) {
console.log(x);
}
}
// a
// b
- 异步 Generator 函数
在语法上,异步 Generator 函数就是async
函数与 Generator 函数的结合。
async function* gen() {
yield 'hello';
}
const genObj = gen();
genObj.next().then(x => console.log(x));
// { value: 'hello', done: false }
- yield* 语句
yield*语句也可以跟一个异步遍历器。
async function* gen1() {
yield 'a';
yield 'b';
return 2;
}
async function* gen2() {
// result 最终会等于 2
const result = yield* gen1();
}