async 是 ES7 提出的新特性,说白了就是 Generator 的语法糖。
既然是语法糖,那我们首先说一下它的改进之处。

1、async 对 Generator 的改进

1.1、写法改进

1
2
3
4
5
6
7
8
Generator函数;
function* foo() {
yield "b";
}
Async函数;
async function foo() {
await "b";
}

对比发现,async 函数在写法上,是将 Generator 函数的星号换成了 async 关键词,yield 关键词换成了 await。

1.2、 内置执行器

Generator 函数的执行必须依靠执行器,而 async 函数自带执行器。所以 async 函数不用像 Generator 函数一样要用 next 方法才会执行,完全可以跟普通函数一样。

1.3、 更好的实用性

co 模块约定,yield 命令后面只能是 Thunk 函数或 Promise 对象,而 async 函数的 await 命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。

1.4、 返回值为 Promise

async 函数返回的是一个 Promise 对象,可以试用 then 方法,不像 Generator 函数返回的是一个 Iterator 对象。

2、async 基本用法

async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句,并且 await 必须要在 async 函数中,不然就会报错。

1
2
3
4
5
async function foo() {
let result = await "b";
return result;
}
foo().then((value) => console.log(value)); // 6

上述可以看出,async 函数中 return 语句返回的值,会成为 then 回调函数的参数。
当 async 函数中抛出错误时,会作为 catch 函数的参数传入。

1
2
3
4
async function foo() {
throw new Error("出错");
}
foo().catch((err) => console.log(err)); // Error: 出错

3、await 表达式

通常来说 await 后边大部分都是 Promise 对象,当时 Promise 对象时,返回的是 Promise 对象 resolved 状态下的结果,如果是基本数据类型时,就是直接返回该结果。

1
2
3
4
5
async function foo() {
let result = await new Promise((resolve) => resolve(1));
return result;
}
foo().then((value) => console.log(value)); // 1

当 await 后边是一个 thenable 对象是,会被当做是 Promise 对象一样处理。

1
2
3
4
5
6
7
8
9
let thenable = {
name: "Jack",
then: (resolve) => resolve("2"),
};
async function foo() {
let result = await thenable;
return result;
}
foo().then((value) => console.log(value)); // 2

await 后面的 Promise 对象出现错误时:

1
2
3
4
5
6
7
async function foo() {
await Promise.reject("出错了");
await Promise.resolve("1");
}
foo()
.then((value) => console.log(value))
.catch((err) => console.log(err)); // 出错了

当有多个 await 时,只要其中一个 await 后的 Promise 报错,那该表达式之后的所有语句将不再执行。如果想要执行之后的语句的话,可以使用 try…catch…

1
2
3
4
5
6
7
8
9
10
11
async function foo() {
try {
await Promise.reject("出错了");
} catch (err) {
console.log(err); // 出错了
}
return await Promise.resolve("1");
}
foo()
.then((value) => console.log(value)) // 1
.catch((err) => console.log(err));

除此之外还有一种方法:

1
2
3
4
5
6
7
async function foo() {
await Promise.reject("出错了").catch((err) => console.log(err)); // 出错了
return await Promise.resolve("1");
}
foo()
.then((value) => console.log(value)) // 1
.catch((err) => console.log(err));

因为 catch 抓的是它之前的错误。
值得注意的是,如果有多个 await 时,这几个 await 并不是同时触发,而是继发,即上一条 await 后的 Promise 成功后再触发下一条 await,如果想同时触发的话,可选用 Promise.all 方法。

1
2
3
4
5
6
7
async function foo() {
let [a, b] = await Promise.all([
new Promise((resolve) => resolve(1)),
new Promise((resolve) => resolve(2)),
]);
return [a, b];
}