Get to know MDN better
要等待的 Promise 实例,Thenable 对象,或任意类型的值。
返回从 Promise 实例或 thenable 对象取得的处理结果。如果等待的值不符合 thenable,则返回表达式本身的值。
拒绝(reject)的原因会被作为异常抛出。
await 通常用于拆开 promise 的包装,使用方法是传递一个 Promise 作为 expression。使用 await 总会暂停当前异步函数的执行,在该 Promise 敲定(settled,指兑现或拒绝)后继续执行。函数的执行恢复(resume)时,await 表达式的值已经变成了 Promise 兑现的值。
若该 Promise 被拒绝(rejected),await 表达式会把拒绝的原因(reason)抛出。当前函数(await 所在的函数)会出现在抛出的错误的栈追踪(stack trace),否则当前函数就不会在栈追踪出现。
await 总会同步地对表达式求值并处理,处理的行为与 Promise.resolve() 一致,不属于原生 Promise 的值全都会被隐式地转换为 Promise 实例后等待。处理的规则为,若表达式:
当一个 Promise 被传递给 await 操作符,await 将等待该 Promise 兑现,并在兑现后返回该 Promise 兑现的值。
若表达式的值不是 Promise,await 会把该值转换为已兑现的 Promise,然后返回其结果。
如果 Promise 被拒绝,则抛出拒绝的原因。
你可以链式调用 catch()(而不是使用 try)以在等待 promise 兑现之前处理被拒绝的 promise。
在模块的顶层,你可以单独使用关键字 await(异步函数的外面)。也就是说一个模块如果包含用了 await 的子模块,该模块就会等待该子模块,这一过程并不会阻塞其他子模块。
下面是一个在 export 表达式中使用了 Fetch API 的例子。任何文件只要导入这个模块,后面的代码就会等待,直到 fetch 完成。
当函数执行到 await 时,被等待的表达式会立即执行,所有依赖该表达式的值的代码会被暂停,并推送进微任务队列(microtask queue)。然后主线程被释放出来,用于事件循环中的下一个任务。即使等待的值是已经敲定的 promise 或不是 promise,也会发生这种情况。例如,考虑以下代码:
对应到 Promise 的写法是:
执行到 await 时,后面的代码就会整体被安排进一个新的微任务,此后的函数体变为异步执行。
对应的 Promise 写法是:
虽然这里的 then() 看起来很多余,其中的代码完全可以被合并到构造器的回调里,但不管该 Promise 的状态如何,then() 的回调总会被异步执行,await 的行为也一样。因此,只要情况不是必须或可能需要等待 Promise 的结果,就应该避免使用 await。
其他微任务能在函数执行恢复之前执行:
此案例中,test() 总会在异步函数恢复执行前被调用,呈现轮流的调度。微任务被执行的顺序通常就是入队的先后顺序,而 console.log("queueMicrotask() after calling async function"); 比 await 晚入队,因此 "queueMicrotask() after calling async function" 在异步函数第一次恢复之后才输出。
有时,当异步函数直接返回一个 Promise 时我们会省略 await。
但是假如这个 Promise 的由来是调用了异步函数,且该异步函数的异步部分抛出了错误:
栈追踪中只出现了 lastAsyncTask,这是因为抛出错误时 noAwait 已经返回——某种意义上该 Promise 已经与 noAwait 无关。若要改善栈追踪,你可以用 await 提前等待,错误就会在函数体结束前抛出,接着该错误会被包装进一个新的 Promise,因错误被 await 在主调函数的函数体抛出,主调函数将会出现在栈追踪。
但是,这样会有一点性能牺牲,毕竟 Promise 会被拆装了又再次包装。
| ECMAScript® 2027 Language Specification # sec-async-function-definitions |
启用 JavaScript 以查看此浏览器兼容性表。