DEBUG.WIKI
雨云;给项目一台云服务器
晚安云;香港 CN2 ¥15/月
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →

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 任务的等待时机和最终结果为准。