异步函数
异步函数,也称为“async/await”(语法关键字),是 ES6 期约模式在 ECMAScript 函数中的应用。这个特性从行为和语法上都增强了 JavaScript,让以同步方式写的代码能够异步执行。
async
使用 async 关键字可以让函数具有异步特征,但总体上其代码仍然是同步求值的。而在参数或闭包方面,异步函数仍然具有普通 JavaScript 函数的正常行为。
1 2 3 4 5 6 7
| async function foo() { console.log(1); } foo(); console.log(2);
|
不过,异步函数如果使用 return 关键字返回了值(如果没有 return 则会返回 undefined),这个值会被 Promise.resolve()包装成一个期约对象。异步函数始终返回期约对象。
1 2 3 4 5 6 7 8 9 10
| async function foo() { console.log(1); return 3; }
foo().then(console.log); console.log(2);
|
异步函数的返回值期待(但实际上并不要求)一个实现 thenable 接口的对象,但常规的值也可以。如果返回的是实现 thenable 接口的对象,则这个对象可以由提供给 then()的处理程序“解包”。如果不是,则返回值就被当作已经解决的期约。
await
因为异步函数主要针对不会马上完成的任务,所以自然需要一种暂停和恢复执行的能力。使用 await 关键字可以暂停异步函数代码的执行,等待期约解决。
await 关键字期待(但实际上并不要求)一个实现 thenable 接口的对象,但常规的值也可以。如果是实现 thenable 接口的对象,则这个对象可以由 await 来“解包”。如果不是,则这个值就被当作已经解决的期约。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| async function foo() { console.log(await 'foo'); } foo();
async function bar() { console.log(await ['bar']); } bar();
async function baz() { const thenable = { then(callback) { callback('baz'); } }; console.log(await thenable); } baz();
async function qux() { console.log(await Promise.resolve('qux')); } qux();
|
停止和恢复执行
下面的例子中按顺序调用了 3 个函数,但它们的输出结果顺序是相反的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| async function foo() { console.log(await Promise.resolve('foo')); } async function bar() { console.log(await 'bar'); } async function baz() { console.log('baz'); } foo(); bar(); baz();
|
async/await 中真正起作用的是 await。async 关键字,无论从哪方面来看,都不过是一个标识符。异步函数如果不包含 await 关键字,其执行基本上跟普通函数没有什么区别。
JavaScript 运行时在碰到 await 关键字时,会记录在哪里暂停执行。等到 await 右边的值可用了,JavaScript 运行时会向消息队列中推送一个任务,这个任务会恢复异步函数的执行。因此,即使 await 后面跟着一个立即可用的值,函数的其余部分也会被异步求值。
1 2 3 4 5 6 7 8 9 10 11 12
| async function foo() { console.log(2); await null; console.log(4); } console.log(1); foo(); console.log(3);
|
注意点
- 必须处理错误
在 async 函数中,如果 await 的 Promise 失败了且没有 try…catch 保护,整个函数的后续逻辑都会死掉。
1 2 3 4 5 6 7 8 9 10 11
| const err = Promise.reject("2");
async function foo() { console.log("1"); await err; console.log("3"); }
foo().catch(console.log);
|
- 循环中的 await
forEach 循环里使用 await,它不会按顺序等待,而是会“并发”。如果需要按顺序执行,请使用传统的 for…of 循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| const now = Date.now(); const sleep = (ms) => new Promise((resolve) => setTimeout(() => { const end = Date.now(); resolve(Math.floor((end - now) / 1000)); }, ms), );
Array.from({ length: 10 }, (_, i) => i).forEach(async (i) => { const res = await sleep(i * 1000); console.log(res); });
async function main() { for (let i = 0; i < 10; i++) { const res = await sleep(i * 1000); console.log(res); } } main();
|
参考资料:
- JavaScript高级程序设计(第4版) 11.3 异步函数