- 开发工具
- 静态分析
- 代码质量
【免费下载链接】flow
Adds static typing to JavaScript to improve developer productivity and code quality.
本篇技术指南以 Flow 仓库内置评估用例 lint_003_unused_promise 为主线,完整讲解unused-promise这个 lint 规则为什么会在 async 函数中报错、如何修复、以及规则底层对不同场景的判定逻辑。读完本文,你将掌握 Flow 中 Promise 值必须被"消费"(await 或显式挂接 then/catch/finally)的规则细节,并能直接复现该评估用例的报错与修复全过程。
一、评估用例:一个最小的修复任务
prompt.md 中给出的任务描述非常简洁:
The code in
main.jsreports a Flow error. Fix it.
这是一个典型的"错误修复(error_fixing)"类评估任务:给定一段在 Flow 下报错的代码,要求将其修复。完整的用例目录结构如下:
evals/evals/01_error_fixing/lint_003_unused_promise/ ├── prompt.md # 任务描述 ├── config.json # 用例元数据与评分标准 ├── input/ # 待修复的输入代码(有错误) └── ideal/ # 修复后的参考实现config.json 明确给出了该用例的定位:
{ "metadata": { "name": "lint_003_unused_promise", "category": "error_fixing", "tags": ["flow", "error_fixing", "lint", "unused-promise"], "difficulty": "medium" }, "grading": { "graders": [ { "type": "contains_ast_node_type", "query": "AwaitExpression" } ] } }其中两个信息值得关注:
- tags 中的
unused-promise:直接点明了要修复的 Flow lint 规则名; - grading 中的
contains_ast_node_type+AwaitExpression:评分标准不是比对字符串或跑测试,而是解析修复后的代码 AST,检查其中是否包含AwaitExpression(await 表达式)节点。这意味着正确的修复必须引入await,单纯删掉报错代码或改写类型声明都无法通过评分。
二、报错代码剖析:Promise 在 async 作用域中被丢弃
待修复的输入代码位于 input/main.js:
// @flow declare function persist(key: string, value: string): Promise<void>; export async function saveAll(entries: Array<[string, string]>): Promise<void> { for (const [key, value] of entries) { persist(key, value); } }代码语义很直白:persist返回Promise<void>,saveAll遍历键值对并逐个调用persist做持久化。问题出在persist(key, value)这一行——调用产生的 Promise 既没有被await,也没有被.then/.catch/.finally处理,直接丢弃。
对于 Flow 的unused-promise规则来说,在 async 函数作用域内出现这种情况会报告:
`Promise` in async scope is unused. Did you mean to `await` it? [unused-promise]这条错误信息本身就给出了修复提示:"你是否打算await它?"。
为什么会报错而不是静默通过
从 Flow 的视角看,Promise 是异步操作的载体,未处理的 Promise 意味着:
- 错误被吞掉:
persist内部如果 reject,异常将无人接住,成为无法观测的静默失败; - 时序未受控:在
saveAll这个async函数中,调用方期望"保存全部完成后再返回",但未 await 的调用无法保证这一契约——saveAll可能在persist尚未完成时就 resolve; - 大概率是编码疏忽:开发者几乎总是"想 await 但忘了写"。
因此规则选择在静态检查阶段直接报错,而不是等到运行时才发现问题。
三、标准修复:为 Promise 加上 await
参考实现位于 ideal/main.js,修复只需改动一行:
// @flow declare function persist(key: string, value: string): Promise<void>; export async function saveAll(entries: Array<[string, string]>): Promise<void> { for (const [key, value] of entries) { await persist(key, value); // 修复:补上 await } }- 改动点:
persist(key, value);→await persist(key, value);; - 修复后语义:每次
persist完成后才进入下一次循环迭代,saveAll保证在所有条目持久化完毕后才会 resolve,并且任何 reject 都会向上传播到saveAll的调用方; - 为什么能通过评分:修复后的代码 AST 中出现了
AwaitExpression节点,命中 config.json 中contains_ast_node_type的查询条件。
值得一提的是,这里的await是在for...of循环内,属于串行执行语义:每一条记录都要等待上一条持久化完成。如果业务上需要并行写盘、最后统一等待,则应先收集 Promise 再Promise.all——那是另一种代码形态,但同样会包含AwaitExpression或等价的处理方式。
四、规则底层:unused-promise 的两种判定场景
unused-promise并非只针对 async 函数。仓库内置的规则测试 tests/unused_promise/test.js 与预期输出 tests/unused_promise/unused_promise.exp 完整刻画了规则的行为边界,可分为两类场景:
场景一:async 作用域(本评估用例所属)
在 async 函数内调用返回 Promise 的函数而不处理,报:
`Promise` in async scope is unused. Did you mean to `await` it? [unused-promise]测试中的对应样例:
async function qux() { foo(); // error —— async 作用域中未 await 的 Promise bar(); // error —— 自定义 Promise 子类同样报错 let x = foo(); let y; y = x; // ok —— 赋值表达式,Promise 被变量持有 }注意两个细节:
- 即使
bar()返回的是class MyPromise extends Promise<void> {}这类Promise 子类,规则同样识别并报错; - 把 Promise 赋给变量是合法的——规则关心的是"Promise 是否被丢弃",只要值被使用(哪怕只是赋值暂存),就不报错。
场景二:同步作用域
在普通(非 async)函数中产生 Promise 而没有任何处理,报:
`Promise` in sync scope is unused. Promises must be handled by calling .then with a rejection handler, .catch, or .finally. [unused-promise]同步作用域下规则明确给出了三条合法出路:.then且必须带 rejection handler(第二个参数)、.catch、或.finally。测试用例对比非常清晰:
function valid() { foo().then(() => {}, () => {}); // ok —— 双参数 then foo().catch(() => {}); // ok foo().finally(() => {}); // ok foo().then(() => {}).then(() => {}, () => {}); // ok —— 链尾有 rejection handler foo().then(() => {}).catch(() => {}); // ok } function invalid() { foo(); // error —— 完全未处理 foo().then(() => {}); // error —— 单参数 then,没有 rejection handler foo().then(() => {}).then(() => {}); // error —— 链尾仍是单参数 then }这里有一个容易被忽略的关键点:单参数.then(() => {})不算正确处理!因为拒绝回调缺失,Promise 链上的错误依然无人接住。规则要求"处理链"的终点必须是带 rejection handler 的.then、.catch或.finally。这一细节解释了为什么修复时补await是最直接的手段——它同时消解了错误吞没与时序失控两个问题。
五、规则对复杂表达式的判定:逻辑运算、三目与可选链
unused-promise还会穿透表达式结构去追踪 Promise 的最终去向,这一点对理解规则边界很有价值(全部样例均来自 tests/unused_promise/test.js):
逻辑表达式&&/?.
function logical(b: boolean) { b && foo(); // error —— 分支中产生未处理 Promise b && foo().catch(() => {}); // ok —— 分支中的 Promise 已被 catch 处理 maybeFoo()?.catch(() => {}) && foo().catch(() => {}); // ok }规则能识别 Promise 是否嵌在逻辑表达式的分支里,也能识别可选链?.上的处理。
三目表达式
function ternary(b: boolean) { b ? foo() : 3; // error —— 其中一个分支丢弃 Promise b ? foo().catch(() => {}) : 3; // ok (b ? foo() : foo()).catch(() => {}); // ok —— 三目整体被 catch }成员方法调用与可选链组合
declare class Foo { foo(): Promise<void>; bar(): ?Promise<void>; // 可空返回 } { declare const x: Foo; x.foo(); // error x.foo().catch(() => {}); // ok x.bar()?.catch(() => {}); // ok —— 可空 Promise 用可选链挂 catch } { declare const x: ?Foo; // 对象本身可空 x?.foo(); // error x?.bar()?.then(() => {}, () => {}); // ok }从这些用例可以归纳出规则的通用原则:只要一个返回Promise(或?Promise)的表达式没有被显式消费,无论它出现在语句、逻辑分支、三目分支还是可选链中,都会触发unused-promise;而一旦挂上了.then(带拒绝处理)/.catch/.finally,或通过await解包,即视为已处理。
六、评估用例如何验证修复:AST 级评分
最后回到本评估用例本身。config.json 中contains_ast_node_type类型的 grader 以AwaitExpression为查询目标,即:
- 解析修复后的
main.js生成 AST; - 在 AST 中查找是否存在
AwaitExpression节点; - 存在即判为修复成功。
这种设计有三个好处:
- 不依赖具体运行环境:无需真实执行
persist,纯静态判定; - 锁定正确修复方向:直接排除"删掉报错行""改成 void 调用"等规避性改法,强制使用
await; - 可组合:
graders是数组,未来可以叠加多个 AST 断言(例如同时要求存在AwaitExpression且不存在某个 lint 错误),扩展性良好。
整个评估框架由 evals/README.md 及仓库中的 run_swebench.py、compile_swebench.py 等脚本支撑,lint_003_unused_promise是其中error_fixing类别、难度 medium 的代表性样例。
七、总结与实战要点
回到lint_003_unused_promise这个任务,核心要点可以收敛为四条:
- 识别症状:async 函数内直接调用返回
Promise的函数而不await/处理,触发unused-promise,报错提示 "Did you mean toawaitit?"; - 标准修复:在调用处补上
await,既满足规则的"消费"要求,又保证异步时序正确与错误传播; - 规则全貌:同步作用域下 Promise 必须通过带 rejection handler 的
.then、.catch或.finally处理,单参数.then不算数;逻辑表达式、三目、可选链中的 Promise 同样被规则追踪; - 验证方式:该评估用例通过 AST 中是否出现
AwaitExpression来判定修复是否成功,鼓励开发者采用"真正 await 异步结果"而非"规避报错"的修复思路。
如果要在本地复现,可在仓库根目录运行 Flow 检查(例如flow check tests/unused_promise/或直接检查该评估用例的input/main.js),观察unused-promise报错;再对照 ideal/main.js 验证修复后的代码不再报错。这套"报错 → 定位 → 补 await → AST 级验证"的流程,同样适用于你日常业务代码中所有被丢弃的 Promise。
- 开发工具
- 静态分析
- 代码质量
【免费下载链接】flow
Adds static typing to JavaScript to improve developer productivity and code quality.
相关推荐
Flow sketchy-null 检查:从 lint 报错到精确判空的修复实践
Flow sketchy null 检查:从 lint 报错到精确判空的修复实践 本文以 Flow 仓库中的评估用例 lint_001_sketchy_null
开发工具静态分析代码质量从报错到修复:Flow Launcher插件兼容性问题的终极解决方案
从报错到修复:Flow Launcher插件兼容性问题的终极解决方案 Flow Launcher是一款Windows平台上的快速文件搜索与应用启动工具,凭借社区
桌面应用插件系统Flow `unnecessary-invariant` 错误修复实战:从 SWE-bench 风格评测用例理解 Flow 的常量条件检查
Flow unnecessary invariant 错误修复实战:从 SWE bench 风格评测用例理解 Flow 的常量条件检查 导读 本文以 Flow
开发工具静态分析代码质量
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考