如何并行执行多个 Promise 并按原始顺序获取结果

3次阅读

如何并行执行多个 Promise 并按原始顺序获取结果

本文介绍使用 `promise.all()` 实现 promise 并行执行与顺序返回的高效方案,替代手动维护队列的复杂逻辑,兼顾性能与可读性。

在 JavaScript 异步编程中,一个常见需求是:同时启动多个异步任务(充分利用并发能力),但确保最终结果严格按任务创建/提交的顺序被处理。例如,在持续拉取链上交易日志(getSwaps())的长时运行流程中,每个日志需异步处理(如休眠模拟 I/O、写入数据库),但数据库写入必须保持原始事件顺序,否则将导致数据错乱。

此时,自行实现类似 PromiseQueue 的串行调度器(如问题中基于递归 #dequeue 的类)虽可行,却存在明显缺陷:

  • 违背并发初衷:所有 Promise 实际被强制串行等待,无法并行执行;
  • 逻辑冗余:需手动管理队列、状态、回调链,易出错且难以调试;
  • 资源浪费:长时间运行场景下,内存中积未 resolve 的 resolve 函数引用,存在潜在泄漏风险。

正确解法是——Promise.all()。它天然满足两大核心要求:
并行执行:所有传入的 Promise 立即启动(无阻塞等待);
顺序返回:结果数组索引严格对应输入 Promise 的声明顺序(非完成顺序)。

以下为优化后的完整实现:

function sleep(ms) {     return new Promise(resolve => setTimeout(resolve, ms)); }  function randomNumber(min, max) {     return Math.floor(Math.random() * (max - min + 1)) + min; }  // 模拟异步数据源(替换为实际的 for await...of 逻辑) async function* getSwaps() {     for (let i = 0; i < 20; i++) {         yield { id: i };     } }  async function run() {     const promises = []; // 存储所有待执行的 Promise 实例      // 【关键】立即创建并推入 Promise(并行启动)     for await (const log of getSwaps()) {         const i = log.id;         const promise = (async () => {             await sleep(randomNumber(300, 1000)); // 模拟异步处理             return i; // 返回结果(将按原始顺序出现在结果数组中)         })();         promises.push(promise);     }      // 【关键】Promise.all 并行等待,结果按 promises 数组索引顺序排列     try {         const results = await Promise.all(promises);         results.forEach((value, index) => {             console.log(`[Index ${index}] Result:`, value);             // ✅ 此处可安全执行顺序敏感操作,如写入数据库             // writeDataToDB(value);         });     } catch (error) {         console.error('At least one promise rejected:', error);         // 注意:Promise.all 遇任一拒绝即整体拒绝,如需容错请改用 Promise.allSettled     } }  run();

⚠️ 重要注意事项

  • 闭包陷阱:示例中 for await 循环内直接使用 log.id 而非循环变量 i(避免经典 var 提升问题),若用普通 for (let i…) 则 let 已确保块级作用域,无需额外处理。
  • 错误处理:Promise.all() 是“全或无”策略——任一 Promise 拒绝,整个调用即拒绝。若需独立处理每个 Promise 的成功/失败状态,请改用 Promise.allSettled(),它始终返回包含 {status, value/reason} 的结果数组。
  • 内存与流控:对“可能运行数小时”的海量数据流(如问题所述),直接累积所有 Promise 到数组可能导致内存溢出。此时应结合分批处理(如每 100 个一组调用 Promise.all)或使用 for await + 限流队列(如 p-map 库),而非完全放弃并行。

✅ 总结

Promise.all() 是解决“并行执行、顺序消费”场景的标准、简洁、高性能方案。它消除了手工队列调度的复杂性,让代码更接近业务意图——专注定义任务,而非调度细节。在保证结果顺序性的前提下,最大化异步并发效率,是现代 JavaScript 异步编程的基石实践之一。

text=ZqhQzanResources