顶层 await 在 Node.js 14.8 前、所有非模块脚本及 CommonJS 中不可用,因 await 仅允许在 async 函数内;自执行函数是兼容方案,需括号包裹、显式赋值、注意导出方式与错误处理差异。

为什么顶层 await 在某些环境里根本用不了
因为 Node.js 14.8 之前、所有浏览器脚本(<script type="module"> 除外)、以及 CommonJS 模块中,await 只能出现在 async function 内部。直接写 await fetch(...) 会报 SyntaxError: await is only valid in async function。这不是写法错,是语法限制——顶层作用域不是异步上下文。
自执行异步函数怎么写才不踩坑
最简写法是 (async () => { /* await stuff */ })();,但要注意三点:
- 必须加括号包裹整个函数表达式,否则 JS 解析器会把它当成函数声明而非立即调用表达式(IIFE),报
Unexpected token '(' - 如果需要导出变量或修改全局状态,别在函数里直接
return,而是显式赋值给window、globalThis或模块级let/const变量 - Node.js 中若用 CommonJS(
.js后缀且无"type": "module"),记得用module.exports或exports.xxx导出结果,不能靠顶层export
示例(浏览器环境):
(async () => {
const res = await fetch('/api/user');
const user = await res.json();
window.currentUser = user; // 显式挂载
})();
和顶层 await 相比,自执行函数有什么代价
主要差在错误处理和执行时机上:
- 自执行函数内抛出未捕获的
Promise拒绝(如网络失败),不会中断后续脚本,但会触发unhandledrejection事件;而顶层 await 失败会让整个模块加载失败(ESM 中表现为ImportError) - 多个自执行函数之间无执行顺序保证,除非手动
await前一个的返回值;顶层 await 是天然串行的 - 打包工具(如 Webpack/Vite)对顶层 await 有优化支持(例如 code-splitting 时延迟加载),自执行函数则被当作普通逻辑,可能提前执行
什么时候该坚持用自执行函数而不是等顶层 await
当你面对这些情况时,绕不开它:
- 目标环境明确不支持顶层 await(比如要兼容 Node.js 12 或旧版 Safari)
- 需要在 CommonJS 模块中初始化异步配置(如读取
fs.promises.readFile的 JSON 配置),又不能改后缀为.mjs - 动态
import()后立刻await并注册插件,但入口文件是传统<script>标签(非 module)
最后提醒一句:自执行函数里的 await 不会阻塞同步代码,但它创建的 Promise 仍会进入微任务队列——别指望它能“同步拿到结果”再往下走。

















