顶层 await 是 ES2022 正式标准,允许在 ESM 模块顶层直接使用 await,使模块变为 异步 模块并按序等待 promise 完成,仅适用于模块环境,不可用于脚本或 Commonjs。

顶层 await 是指在 ecmascript 模块(ESM)的最外层 作用域(即模块顶层)直接使用 await 表达式,而无需将其包裹在 async 函数中。它自 ES2022(第 13 版)起成为正式标准,让模块可以“等待”异步操作完成后再完成自身求值,从而简化依赖异步初始化的场景。
顶层 await 的核心特性
它只在模块顶层有效,且会使该模块变成一个“异步模块”。一旦模块中出现顶层 await,整个模块的执行会被暂停,直到所有顶层 await 的 Promise 都 fulfilled。其他导入该模块的代码也会等待其完全就绪后才继续执行。
- 不能在脚本(
<script></script>非模块)或 CommonJS 中使用 - 模块加载器会将含顶层 await 的模块视为
async module,其import返回的是 Promise - 多个顶层
await会按顺序执行(串行),但可配合Promise.all()并行发起
如何在模块中使用顶层 await
只需把 await 写在模块最外层即可,常用于初始化配置、获取远程数据、等待资源加载等。
例如:
立即学习“Java 免费学习笔记(深入)”;
// config.mjs const response = await fetch('/api/config'); const config = await response.json(); export { config};
另一个常见用法是并行加载多个资源:
// data.mjs const [usersRes, postsRes] = await Promise.all([fetch('/api/users'), fetch('/api/posts') ]); export const users = await usersRes.json(); export const posts = await postsRes.json();
使用时需要注意的限制
顶层 await 不是万能的,有明确的边界和约束:
- 无法在函数、条件语句、循环 内部使用(那些地方本来就支持
await,但必须在async函数里) - 不能与
var声明混用(因提升机制冲突),推荐统一用const或let - 动态
import()可与顶层 await 结合,实现按需 异步加载 子模块 - node.js 需启用 ESM(如文件扩展名为
.mjs或"type": "module"),浏览器 需通过<script type="module"></script>加载
对模块依赖链的影响
如果模块 A 顶层 await 了某 Promise,而模块 B 导入 A,那么 B 的执行会自动等待 A 就绪。这种“隐式等待”简化了异步初始化流程,但也要求开发者清楚模块加载时序——比如不能在顶层试图读取尚未 resolve 的导出值。
例如,下面写法是安全的:
// main.mjs import {config} from './config.mjs'; console.log(config); // ✅ 此时 config 已确定存在且已解析完成
但如果错误地在非模块环境或未正确声明类型的地方使用,会直接报语法错误:await is only valid in async functions and the top level bodies of modules。