14 async 函数

88 阅读4分钟
├── async 函数
│   ├── 含义
│   │     └─ async 函数是什么?一句话,它就是 Generator 函数的语法糖。
│   │     └─ `async`函数对 Generator 函数的改进,体现在以下四点。
│   │         └─ (1)内置执行器。 Generator 函数的执行必须靠执行器,所以才有了`co`模块,而`async`函数自带执行器。也就是说,`async`函数的执行,与普通函数一模一样,只要一行。
│   │         └─ (2)更好的语义。 `async``await`,比起星号和`yield`,语义更清楚了。`async`表示函数里有异步操作,`await`表示紧跟在后面的表达式需要等待结果。
│   │         └─ (3)更广的适用性。 `co`模块约定,`yield`命令后面只能是 Thunk 函数或 Promise 对象,而`async`函数的`await`命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
│   │         └─ (4)返回值是 Promise`async`函数的返回值是 Promise 对象,这比 Generator 函数的返回值是 Iterator 对象方便多了。你可以用`then`方法指定下一步的操作。
│   ├── async 函数有多种使用形式。
│   │     └─ 函数声明 async function foo() {}
│   │     └─ 函数表达式  const foo = async function () {};
│   │     └─ 对象的方法  let obj = { async foo() {} };
│   │     └─ Class 的方法
│   │     └─ 箭头函数 const foo = async () => {};
│   ├── 语法
│   │     └─ 返回 Promise 对象
│   │         └─ `async`函数内部`return`语句返回的值,会成为`then`方法回调函数的参数。
│   │         └─ `async`函数内部抛出错误,会导致返回的 Promise 对象变为`reject`状态。抛出的错误对象会被`catch`方法回调函数接收到。
│   │     └─ Promise 对象的状态变化
│   │         └─ `async`函数返回的 Promise 对象,必须等到内部所有`await`命令后面的 Promise 对象执行完,才会发生状态改变,除非遇到`return`语句或者抛出错误。也就是说,只有`async`函数内部的异步操作执行完,才会执行`then`方法指定的回调函数。
│   │     └─ await 命令
│   │         └─ 正常情况下,`await`命令后面是一个 Promise 对象,返回该对象的结果。如果不是 Promise 对象,就直接返回对应的值。 
│   │         └─ `await`命令后面是一个`thenable`对象(即定义了`then`方法的对象),那么`await`会将其等同于 Promise 对象。
│   │         └─ `await`命令后面的 Promise 对象如果变为`reject`状态,则`reject`的参数会被`catch`方法的回调函数接收到。
│   │         └─ 任何一个`await`语句后面的 Promise 对象变为`reject`状态,那么整个`async`函数都会中断执行。
│   │         └─ 有时,我们希望即使前一个异步操作失败,也不要中断后面的异步操作。这时可以将第一个`await`放在`try...catch`结构里面,这样不管这个异步操作是否成功,第二个`await`都会执行。
│   │         └─ 另一种方法是`await`后面的 Promise 对象再跟一个`catch`方法,处理前面可能出现的错误。
│   │     └─ 错误处理
│   │         └─ 如果`await`后面的异步操作出错,那么等同于`async`函数返回的 Promise 对象被`reject`。
│   │         └─ 防止出错的方法,也是将其放在`try...catch`代码块之中。
│   │         └─ 如果有多个`await`命令,可以统一放在`try...catch`结构中。
│   │     └─ 使用注意点
│   │         └─ 第一点,前面已经说过,`await`命令后面的`Promise`对象,运行结果可能是`rejected`,所以最好把`await`命令放在`try...catch`代码块中。
│   │         └─ 第二点,多个`await`命令后面的异步操作,如果不存在继发关系,最好让它们同时触发。
│   │             └─ let [foo, bar] = await Promise.all([getFoo(), getBar()]);
│   │         └─ 第三点,`await`命令只能用在`async`函数之中,如果用在普通函数,就会报错。
│   │         └─ 第四点,async 函数可以保留运行堆栈。
│   ├── async 函数的实现原理
│   │     └─ async 函数的实现原理,就是将 Generator 函数和自动执行器,包装在一个函数里。
│   ├── 顶层 await
│   │     └─ 根据语法规格,`await`命令只能出现在 async 函数内部,否则都会报错。
│   │     └─ 注意,顶层`await`只能用在 ES6 模块,不能用在 CommonJS 模块。这是因为 CommonJS 模块的`require()`是同步加载,如果有顶层`await`,就没法处理加载了。
│   │     └─ 如果加载多个包含顶层`await`命令的模块,加载命令是同步执行的。
│   │     └─ 顶层的`await`命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。