顶层 await 就是在模块顶层直接写 await,不用包 async 函数。
核心规则:
① 只在 ES Module(type="module")里能用
② 顶层 await 会等异步代码跑完再执行
坑:普通 script 不能用、会阻塞模块加载、import 也能配合 await。
以前想等一个 Promise,得专门包一个 async 函数再马上调用,多写两行。现在模块最外层直接 await。
// 一个返回 Promise 的函数
function getData() {
// 造一个承诺
return new Promise(resolve => {
// 500 毫秒后兑现
setTimeout(() => resolve("数据来了"), 500);
});
// 以前:非得套个 async 函数再调用
}
// 必须包在 async 函数里
async function load() {
// await 只能在 async 里
let data = await getData();
// 等到了再打印
console.log(data);
}
// 还得手动调一下
load();
// 现在:模块最外层直接等(ES2022)
// 不用包函数,直接等
let data = await getData();
// 等到了再打印
console.log(data);
少一层 async function + 调用 的壳,加载配置、初始化数据时更直接。
顶层 await 不是随便哪个 script 都能用的——只有把当前文件当成 ES 模块才行。
// 要这么引用才是"模块",才能用顶层 await < script type = "module" // 普通 <script> 里写顶层 await src = "main.js" > < /script> / / 或 package.json 里 "type": "module"
如果你在普通 <script> 里直接写顶层 await,浏览器会报"await 只在模块顶层才允许"。用 Vite / Webpack / 原生 type="module" 的项目里很常见;普通 script 里想等结果,还是得包 async 函数。
// main.js(模块) // 等它完成(顶层 await 会卡住后面代码) let data = await fetchData(); // 之后才执行 —— 依赖顺序有保证 initPage(data);
要注意:顶层 await 会阻塞模块里后面的代码执行,但不会阻塞其他模块。适合"必须先拿到数据才能初始化"的场景——比如先加载用户配置再渲染页面。
① 可能在什么地方用:模块加载时先拉配置/翻译表再初始化、Vite/Webpack 项目里做异步依赖初始化、动态 import 后直接 await 拿模块。
② 常见的问题:普通 <script> 里写顶层 await 直接报错;误以为顶层 await 会阻塞整个浏览器(其实只阻塞同模块);顶层 await 后面的代码被延迟导致界面白屏。
③ 解决思路:用顶层 await 前先确认 type="module";普通脚本继续用 (async => { ... }) 包装;初始化重活放后面,别让顶层 await 拖慢首屏。
| API | 作用 | 参数 | 返回值 | 代码示例 |
|---|---|---|---|---|
| 顶层 await | 在 ES 模块最外层直接等结果 | 一个 Promise | 它的结果 | await getData(); |
| type="module" | 只有 ES 模块最外层才允许直接 await | 写在 script 标签上 | 一个 ES 模块 | <script type="module"> |