Node.js · 原创实验
Promise.all 已经进 catch,文件为什么还在写?
让一个任务失败,另外两个任务继续写文件;再加入协作取消,比较 catch 时与收尾后的目录。附两轮实际记录,说明聚合失败、取消和回滚的边界。
记录时间: (北京时间) · Node v22.22.3 · darwin 27.0.1 / arm64
文章语言: English
Promise.all 的 catch 已经执行,目录里却又多出两个文件。这时先检查其余异步函数是不是早就启动了。
这个实验把三个任务放进同一个数组:第一个立即失败,另外两个已经进入函数,停在一个可手动放行的 Promise 上。catch 执行时目录为空;放行并等待所有任务收尾后,a.txt 和 b.txt 都出现了。
两轮实际结果
| 情况 | 第 1 轮 | 第 2 轮 |
|---|---|---|
| 只接住 Promise.all 的拒绝 | catch 时 0 个文件;收尾后 2 个;任务状态 rejected/fulfilled/fulfilled | catch 时 0 个文件;收尾后 2 个;任务状态 rejected/fulfilled/fulfilled |
| catch 中发信号,写入前检查 | catch 时 0 个文件;收尾后 0 个;任务状态 rejected/rejected/rejected | catch 时 0 个文件;收尾后 0 个;任务状态 rejected/rejected/rejected |
| 文件已经写完,再调用 abort | abort 后仍有 1 个已写入文件 | abort 后仍有 1 个已写入文件 |
启动任务的是函数调用
数组里的异步函数在调用时就开始执行了。Promise.all 接收的是这些调用已经返回的 Promise。它可以尽早把拒绝原因交给调用者,但这不会给其他任务自动插入停止指令。
为了看清顺序,脚本没有靠几十毫秒的延时赌先后。两个写入任务都要等 gate 放行。主流程先等到聚合失败,读取一次目录,再放行,最后用 allSettled 等它们全部结束。下面用函数名缩写调度顺序,完整实现见下载脚本。
const tasks = [failImmediately(), writeAfterGate('a.txt'), writeAfterGate('b.txt')];
try {
await Promise.all(tasks);
} catch (error) {
// 此时只确认聚合 Promise 已拒绝。
}
releaseGate();
await Promise.allSettled(tasks);把取消信号接到真正产生副作用的位置
第二组在 catch 中调用 controller.abort()。两个写入任务被放行后,先检查 signal,再决定是否调用 writeFile。两轮结果都没有生成文件,三个任务最终全部 rejected。
这个结果有明确的前提:abort 发生在两个任务检查信号之前。控制器不认识你的任务逻辑,任务必须主动检查信号,或者把信号交给支持取消的底层操作。只创建一个 AbortController 不会改变其他 Promise 的行为。
await gate.promise;
controller.signal.throwIfAborted();
await writeFile(target, 'written after aggregate rejection');已经落盘的文件还在
第三组先写好 already.txt,再发出 abort。读取目录时,这个文件仍然存在。取消信号没有删除它,也没有把写入恢复到之前的状态。
需要整批成功才交付时,可以先把结果写到这次操作专用的临时目录,确认收尾状态后再决定交付或清理。跨系统的写入则要分别考虑幂等和补偿。本例只验证本地文件的存在,不替这些方案提供事务保证。
报错后仍要安排收尾
如果后续逻辑要读取这批任务产生的文件,catch 后立即继续运行,读到的目录可能还在变化。实验使用 allSettled 等待收尾,再检查结果;它负责汇总状态,同样不会主动取消任务。
下载脚本后可以先去掉写入前的信号检查,观察第二组怎样重新产生文件。失败断言会让脚本停止。这个改动能直接检验:起作用的是任务对信号的配合,而不是外层多写了一次 catch。
下载脚本,在自己的机器上复跑
使用 Node.js 22 或更高版本,不需要安装依赖。脚本只使用 Node 内置模块;每次执行都会跑两轮并检查断言。记录路径必须尚不存在,避免覆盖上次结果。
node --version
node run.mjs
node run.mjs --record my-results.json三种情况都使用自建任务和新建临时目录,跑完后只清理本轮创建的目录。顺序由 gate 控制,没有验证磁盘性能、远程请求取消或数据库事务;信号检查与真实副作用之间的竞态仍要按具体操作处理。
这是同一操作者在本机完成的两轮记录,尚无独立读者复跑反馈。换环境后请保留自己的输出;断言失败会终止脚本。
核对脚本 SHA-256
571f4777b8e9bf25e2f7277e73e5659886e2004e5f37c50312ad0eac585e5bab
摘要对应页面提供的脚本文件内容。完整运行数据在 verification.json。