
eslint-plugin-unicorn 的 prefer-top-level-await 规则用顶层 await 取代顶层 Promise 链与异步函数调用【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn导读prefer-top-level-await是 eslint-plugin-unicorn 中专门处理「模块顶层异步代码」的一条规则它鼓励开发者在 ESM 模块顶层直接用await书写异步逻辑替换掉「立即执行异步函数async IIFE」「先声明 async 函数再调用」「裸奔的.then()/.catch()Promise 链」这三种常见写法。读完本文你将掌握该规则的完整触发场景、自动修复suggestion行为、void与变量初始化等豁免边界以及针对.cjs、.svelte文件的特殊处理并能在自己的工程中直接套用正确的写法。为什么顶层await更值得推荐文档开篇给出的核心理由是顶层 await 可读性更强并且能避免未处理的 Promise rejectionunhandled rejections。可读性await run()是平铺的线性代码而(async () { await run(); })()或run().catch(...)多了一层包装阅读者需要额外解析「包了一层作用域 / 挂了一条回调链」的意图。健壮性顶层await会把 rejection 交给模块加载环境统一处理而手写的.catch()一旦遗漏分支Promise 被静默丢弃容易产生难以追踪的 unhandled rejection。在 ESM 模块type: module或.mjs文件中顶层await是标准语法该规则正是面向这类可以安全使用顶层 await 的代码环境设计的。规则配置与元信息从规则源码 rules/prefer-top-level-await.js 可以确认meta.typesuggestion即该规则报告的是风格与健壮性建议不涉及明确的功能错误。docs.descriptionPrefer top-level await over top-level promises and async function calls.docs.recommendedunopinionated同时被收录进recommended与unopinionated两套推荐配置也就是说默认开启、无需额外配置。hasSuggestionstrue规则通过 ESLint 的「编辑建议suggestion」机制提供手动修复能力对应文档头部的 标记而非自动 fix。languages[js/js]作用于标准 JavaScript 文件。规则在 rules/index.js 中以prefer-top-level-await名称导出。启用方式与其它 unicorn 规则一致在 ESLint 配置中引用unicorn/recommended即可参见 readme.md 中关于 recommended 配置的说明。三种被禁止的写法与推荐写法1. 立即执行异步函数async IIFE文档给出的典型反面例子// ❌ (async () { try { await run(); } catch (error) { console.error(error); process.exit(1); } })();源码中将这类表达式归类为ERROR_IIFE消息为Prefer top-level await over an async IIFE.rules/prefer-top-level-await.js。判定逻辑位于 rules/prefer-top-level-await.js只要调用表达式的 callee 是FunctionExpression或ArrowFunctionExpression、带有async且不是 generator就会被报告报告位置指向函数头部getFunctionHeadLocation。2. 声明 async 函数后在顶层调用// ❌ async function main() { try { await run(); } catch (error) { console.error(error); process.exit(1); } } main();对应消息为ERROR_IDENTIFIERPrefer top-level await over an async function {{name}} call.。这里需要特别注意规则的边界条件rules/prefer-top-level-await.jscallee 必须是纯Identifier该标识符必须恰好只有一个定义variable.defs.length 1避免误报重名或复杂绑定定义值必须是const声明的 async 非 generator 函数箭头函数或函数表达式。let/var以及using/await using不会被报告这是规则「刻意只处理 const」的设计源码注释明确说明了这一点测试也专门锁定该行为由于typescript-eslint/parser不会填充definition.kind源码在 rules/prefer-top-level-await.js 处回退读取外层VariableDeclaration的kind以保证 TypeScript 解析器下行为一致对应 issue #2946测试中也有专门的锁定用例。对这种情况规则还附带一条编辑建议Insert awaitSUGGESTION_ADD_AWAIT修复方式是在调用表达式前插入awaitrules/prefer-top-level-await.js。3. 顶层 Promise 链// ❌ run().catch(error { console.error(error); process.exit(1); });对应ERROR_PROMISEPrefer top-level await over using a promise chain.。判定基于promisePrototypeMethods [then, catch, finally]rules/prefer-top-level-await.js凡是顶层调用这三个方法之一且其返回值未被await、未被void丢弃、未赋值给变量就会触发报告。测试 test/prefer-top-level-await.js 覆盖了大量变体foo.then(bar)、foo?.then(bar)、foo.then?.(bar)、(async () {})().catch(...)、foo.then().toString()、!foo.then()等均被判定为 invalid。推荐的最终形态// ✅ try { await run(); } catch (error) { console.error(error); process.exit(1); } // ✅ await run();顶层await直接放在模块顶层try/catch负责错误处理代码层级被抹平同时错误不再可能被静默丢弃。豁免边界哪些代码不会被报告1.void显式丢弃的调用被视为「即发即忘」文档明确说明用void运算符显式丢弃结果的调用被当作有意的 fire-and-forget 工作而忽略。例如void (async () {})()、void foo.then(bar)都是合法的。底层实现位于 rules/utils/is-call-expression-value-discarded-with-void.js它会沿调用链向上遍历跳过成员调用、ChainExpression与 TypeScript 表达式包装最终检查外层是否为UnaryExpression且运算符为void。测试中可见void (async () {})().catch(handleError)等均为 valid。2. 变量初始化先存 Promise 后 await 是允许的文档给出这一种「看似违反、实则放行」的模式// ✅ const preparationDone prepareSomething(); export async function doSomething() { await preparationDone; }源码通过isVariableDeclaratorInitializerrules/prefer-top-level-await.js识别「调用结果直接作为变量初始化器」的情况并跳过从而支持「先启动异步任务、稍后再等待」的并行预热模式。类似的放行还包括调用已是await的参数isAwaitExpressionArgument、位于Promise.all/allSettled/any/race([...])数组内的调用isInPromiseMethodsrules/prefer-top-level-await.js。3. 非顶层位置函数、类内部不适用规则只在真正的模块顶层生效。isTopLevelCallExpressionrules/prefer-top-level-await.js会向上遍历祖先节点一旦遇到任何函数含箭头函数、方法或ClassDeclaration/ClassExpression即返回 false。因此类字段中的promise.then(bar)、static { promise.then(bar) }、普通函数体内的调用都不会被报告对应测试见 test/prefer-top-level-await.js。4. Zod schema 的.catch()特判.catch()也是 Zod 等 schema 校验库的常用方法与 Promise 链语义完全不同。源码内置了一套 schema 识别逻辑isSchemaCatchObjectrules/prefer-top-level-await.js以z命名空间或schema/xxxSchema结尾的标识符为根仅允许catch/default/nullable/nullish/optional等方法组合遇到parse/safeParse/spa、Async后缀或coerce等终止性/命名空间方法则判定不是 schema 而照常报告。因此z.string().catch()、someSchema.optional().catch(fallback)是 valid而z.string().parse(value).catch(handle)、z[string]().catch(fallback)会被报告测试见 test/prefer-top-level-await.js 与 test/prefer-top-level-await.js。5..cjs与.svelte文件整体跳过规则开头rules/prefer-top-level-await.js使用context.physicalFilename真实文件路径避免被 processor 或代码块藏匿绕过做小写后缀检查命中.cjs或.svelte时直接返回、完全不启用规则。理由有二.cjs是 CommonJS 模块不存在顶层await语法报告只会制造无法应用的噪声.svelte组件脚本有框架特有的顶层await行为如 Svelte 5 的$state(loadCached()){#await}块不应被强行改写。对应测试见 test/prefer-top-level-await.js。从测试用例看规则细节测试文件 test/prefer-top-level-await.js 将规则拆成 Async IIFE、Promise、Identifier、InPromisemethods 四组快照测试以下几点对理解规则边界很有价值可选链也逃不掉(async () {})?.()、foo?.()、foo?.then(bar)都会被报告规则的 AST 遍历不因?.而中断。TypeScript 包装表达式会被剥开(foo.then(bar) as Promisevoid)、(async () {})() satisfies Promisevoid均被视为普通调用予以报告通过isTypeScriptExpressionWrapper统一解包但await (foo.then(bar) as Promisevoid)、void (foo.then(bar) as Promisevoid)依然分别被豁免。重名与多定义不报同一作用域内重复声明 async 函数后调用不会误报const bar foo; bar()引用别名也不会触发因为规则只跟踪「调用标识符自身的单一 const 定义」。Promise.all 内部的宽松规则await Promise.all([foo(), foo.then(bar)])整体 valid因为把这些调用改成单独的顶层 await 反而会破坏并发语义但await Promise.all([runAsync() value])把 Promise 塞进布尔表达式仍会被报告。实战建议迁移路径将顶层(async () { ... })()和main()模式改为直接await将run().catch(handler)改为await run()并配合try/catch。需要真正并行时保留const p1 taskA(); const p2 taskB(); await p1; await p2;的「先启动后等待」写法这正是规则允许的模式。确实要即发即忘时显式写void foo()向读者和规则同时声明「这里故意不等待」。注意文件类型CommonJS.cjs与 Svelte.svelte文件不会被该规则检查无需特意关闭若你在.js文件中仍使用 CommonJS 模块请确保文件后缀为.cjs以免产生误报。结合编辑建议修复由于规则是 suggestion 级别IDE 中可通过「快速修复」插入await或手动补上await关键字。总结prefer-top-level-await从「可读性」与「rejection 安全」两个角度出发把模块顶层的三种异步样板代码收敛为直接的顶层await同时它通过void豁免、变量初始化豁免、函数/类作用域豁免、Zod schema 特判以及.cjs/.svelte文件跳过保证了误报率极低。理解其判定逻辑与豁免边界能让你放心地在 ESM 工程中启用该规则写出更扁平、更不容易漏掉错误处理的顶层异步代码。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考