zone.js 0.16.3 的 Promise.all(Set) 为什么提前返回空数组
CommonJS 加载的 Zone UMD 用 length 遍历输入,Set 中的 Promise 可能被跳过。检查实际产物,转换稳定任务快照,或按测试预设配置受支持的 ESM。
数据库迁移测试还没写完数据,Promise.all() 就已经完成;把待处理 Promise 放进数组却正常。若测试通过 CommonJS 加载 zone.js 0.16.3,并把一个 Set 直接传给 Promise.all(),检查实际加载的 UMD 产物。
这个组合会把非空 Set 当成没有任务,结果可能是 []。它与漏写 await 不同:即使调用方正确等待了 Promise.all(),被加载的实现也可能根本没有遍历 Set 中的 Promise。
同一版本的两份产物,遍历方式不同
0.16.3 的包为 CommonJS 提供 bundles/zone.umd.js,另有 ESM 产物。UMD 内部的 ZoneAwarePromise.allWithCallback 使用数组式遍历:检查输入的 length,再按数字下标读取。
Set 有 size 和迭代器,没有数组的 length。循环因此不进入,内部的待完成计数直接归零,外层 Promise 提前完成。正式源码中的 for...of 与已发布 UMD 的这段循环不能混为一谈;只看 TypeScript 源码,容易漏掉构建产物的差异。
最小检查可以放在相同的测试初始化环境中:
require('zone.js');
Promise.all(new Set([Promise.resolve(1)])).then((values) => {
console.log(values);
});
正常结果应是含有 1 的数组。如果这里得到 [],再检查测试预设、模块映射和最终解析路径。项目使用了 Angular,并不能自动说明 Jest 加载的是浏览器应用使用的同一份 Zone 产物。
能修改调用处时,明确传入数组
应用自己维护待处理集合时,可以在调用边界转换:
const tasks = Array.from(pendingWrites);
const results = await Promise.all(tasks);
pendingWrites 是已有的 Promise 集合,这段代码放在异步函数中。数组在转换时取得集合快照;转换后才加入 Set 的新任务,不会自动进入这次等待。迁移或批量写入流程必须明确什么时候停止添加任务,不能把这处转换当成动态任务队列。
集合位于第三方库内部时,不应到处给 Promise 打补丁。优先核对当前 jest-preset-angular 版本的 ESM 配置,把测试环境一致地迁到受支持的 ESM 加载方式,再确认实际产物。不要在已初始化的 Zone 测试环境中简单追加一次 import('zone.js'),以为第二次加载就一定替换了先前补丁。
用尚未完成的任务检查等待契约
只用立即成功的 Promise 看结果不够。还应让集合中一个任务保持 pending,确认聚合 Promise 在它完成前不会完成;再覆盖拒绝路径,确认错误仍能传回测试调用方。
涉及数据库写入时,提前完成可能已经使清理逻辑与写入重叠。先核对现有测试数据和迁移记录,再重跑获准的测试,避免把写入失败直接变成整批重复迁移。
截至 2026 年 10 月 23 日,0.16.3 仍是已核对的正式包版本,不能假定它的 CommonJS 产物已改变。验收以当前测试进程实际加载的文件、pending 任务的等待时机和最终结果为准。


