顶层 await(top-level await)
ES 提案 top-level await 允许开发者在 async
函数外部使用 await
字段。它就像巨大的 async
函数,原因是 import
它们的模块会等待它们开始执行它的代码。
过去
当 async/await
首次引用时,尝试在 async
函数外部使用 await
的结果是产生 SyntaxError
。大多数开发者使用立即执行异步函数表达式的方式来使用该功能。
await Promise.resolve(console.log('🎉'))
// → SyntaxError: await is only valid in async function
;(async function () {
await Promise.resolve(console.log('🎉'))
// → 🎉
})()
现在
随着顶层 await
的支持,下面的代码可以替换 modules 中的常用代码:
await Promise.resolve(console.log('🎉'))
// → 🎉
注意:顶层
await
仅能工作在模块的顶层。在 class 代码块或非async
函数不支持。
何时使用
这些情况借鉴自 spec proposal repository
动态依赖导入
const strings = await import(`/i18n/${navigator.language}`)
这允许在模块的运行时环境中确认依赖项。在像开发环境/生产环境切换,国际化,环境切换等等情况时非常有用。
资源初始化
const connection = await dbConnector()
这允许模块申请资源,同时也可以在模块不能使用时抛出错误。
依赖回退
下面的例子希望从 CDN A 加载 JavaScript 库,如果它加载失败 CDN B 将是备份选择:
let jQuery
try {
jQuery = await import('https://cdn-a.example.com/jQuery')
} catch {
jQuery = await import('https://cdn-b.example.com/jQuery')
}
模块执行顺序
JavaScript 中一个使用 await
的巨大改变是模块树执行顺序。JavaScript 引擎在 post-order traversal(后顺序遍历) 中执行模块:先从模块树左侧子树开始,模块被执行,导出它们的绑定,然后同级也被执行,接着执行父级。该算法递归运行,直到执行模块树的根。
在顶层 await
之前,此顺序始终是同步的和确定性的:在代码的多次运行之间,可以保证代码树以相同的顺序执行。await
到达顶层后,就存在相同的保证,除非你不使用顶层 await
。
这里是在模块中使用顶层 await
时发生的事:
- 执行当前模块直到
await
promise 完成状态。 - 执行父模块直到子模块执行完
await
,包括所有的同级模块执行完,并导出绑定。 - 假设代码树中没有周期或其它
await
promise,对于同级模块,包括同级的父模块,将可能在相同的同步顺序中继续执行。 - 在
await
promise 完成后,被调用的模块将继续执行await
。 - 只要没有其他
await
promise ,父模块和子树将继续以同步顺序执行。
顶层 await
能在 DevTools 中执行?
情况确实这样!在 Chrome DevTools, Node.js 和 Safari 的 REPL 中支持顶层 await
有一段时间了。但是该方法还不是标准并且限制 REPL!它明确来自 top-level await
提案。生产代码的测试依赖顶层 await
的标准提案的语义,认真测试你的实际应用,不能仅仅依靠 DevTools 或者 Node.js 的 REPL!
译注:REPL 即 read–eval–print loop,译作 “读取-求值-输出”循环,了解更多查看 wikipedia
顶层 await
是臭脚枪吗?
你也许已经看到 Rich Harris 的 Top-level await 臭名昭著的问题,它最初概述了许多有关顶层 await
的问题,并提出 JavaScript 语言不要实现该功能。await
的一些具体的问题是:
- 顶层
await
会阻断执行。 - 顶层
await
会阻断资源请求。 - CommonJS 模块没有确定如何实现。
stage 3 的提案解决了这些问题:
- 同级之间可以执行,最终不会阻断。
- 顶层
await
发生在模块图的执行阶段。至此,所有资源均已获取并链接。没有阻塞获取资源的风险。 - 顶层
await
仅限于 ES 模块。明确不支持脚本或 CommonJS 模块。
作为新的语言功能,总会有一些不可意料的行为。举例来说,使用顶层 await
时,循环模块依赖关系可能会导致死锁。
没有顶层 await
时,JavaScript 开发者经常使用 async 立即执行函数表达式仅仅是为了使用 await
。不幸的是,这种模式导致图形执行和应用程序的静态可分析性的确定性降低。由于这些原因,缺少顶层 await
被认为比该功能带来的危害有更高的风险。
支持顶层 await
- Chrome 89+
- FireFox
- Safari 15+
- Babel
- Node.js 14+
本文由 吳文俊 翻译,原文地址 Top-level await