Node.js 异步任务协作:7 种实用方案与真实项目案例

张开发
2026/4/10 2:09:34 15 分钟阅读

分享文章

Node.js 异步任务协作:7 种实用方案与真实项目案例
多个独立异步任务如何高效协作从Promise.all到队列控制本文用 7 个真实场景给出答案。在 Node.js 开发中我们经常需要同时处理多个独立的异步任务读取多个配置文件、调用多个外部接口、批量上传文件……这些任务彼此独立但最终结果需要协同处理。如果只是简单地逐个await性能会大打折扣如果盲目并发又可能引发资源耗尽或错误处理混乱。本文将介绍 7 种成熟的异步协作方案每种都配有真实项目中的代码示例帮助你快速应用到实际工作中。1.Promise.all—— 应用启动加载必要配置场景服务启动时必须读取数据库、Redis 和第三方密钥三个配置文件任何一个缺失或格式错误都不能继续启动。const fs require(fs).promises; async function loadConfigs() { const [db, redis, secrets] await Promise.all([ fs.readFile(./config/db.json, utf8).then(JSON.parse), fs.readFile(./config/redis.json, utf8).then(JSON.parse), fs.readFile(./config/secrets.json, utf8).then(JSON.parse) ]); console.log(所有配置加载完成, { db, redis, secrets }); }特点全成功或全失败结果以数组顺序返回。适合“缺一不可”的场景。2.Promise.allSettled—— 批量同步用户数据到多个外部系统场景用户更新个人资料后需要同步到 CRM、邮件服务、推送系统。允许个别失败但要记录失败原因后续重试。async function syncUserToExternal(user) { const tasks [ syncToCRM(user), syncToEmailService(user), syncToPushService(user) ]; const results await Promise.allSettled(tasks); const failed results.filter(r r.status rejected); if (failed.length) { console.error(同步失败 ${failed.length} 个系统, failed.map(f f.reason)); // 将失败记录到数据库等待重试队列处理 } return results; }特点等待所有任务完成无论成功或失败都能拿到每个任务的最终状态。3.Promise.race—— HTTP 请求超时控制场景调用外部 API必须在 3 秒内返回结果否则自动降级使用缓存数据。function fetchWithTimeout(url, timeout 3000) { const controller new AbortController(); const fetchPromise fetch(url, { signal: controller.signal }); const timeoutPromise new Promise((_, reject) setTimeout(() { controller.abort(); reject(new Error(请求超时)); }, timeout) ); return Promise.race([fetchPromise, timeoutPromise]); } // 使用 try { const data await fetchWithTimeout(https://slow-api.example.com/data, 3000); console.log(data); } catch (err) { console.log(使用缓存数据); }特点只取最先完成的那个结果成功或失败。常用于超时控制、多源竞速。4.Promise.any—— 多 CDN 资源容灾加载场景前端静态资源部署在三个 CDN 上只要任意一个 CDN 返回成功就使用该资源忽略失败的 CDN。async function loadScriptFromCDNs(urls) { const fetchTasks urls.map(url fetch(url).then(res { if (!res.ok) throw new Error(HTTP ${res.status}); return res.text(); })); try { const scriptContent await Promise.any(fetchTasks); eval(scriptContent); // 实际项目中建议使用更安全的方式 console.log(脚本加载成功); } catch (aggregateError) { console.error(所有 CDN 均不可用, aggregateError.errors); } } loadScriptFromCDNs([ https://cdn1.example.com/lib.js, https://cdn2.example.com/lib.js, https://cdn3.example.com/lib.js ]);特点只要有一个成功就返回全部失败才抛出异常。非常适合冗余容灾设计。5. 事件计数器 —— 旧式多文件写入完成后合并压缩场景维护一个老项目基于回调风格需要等三个日志文件全部写入磁盘后再执行合并压缩操作。const EventEmitter require(events); const fs require(fs); class FileWriter extends EventEmitter { writeAndNotify(file, data) { fs.writeFile(file, data, (err) { if (err) this.emit(error, err); else this.emit(done, file); }); } } // 应用 const writer new FileWriter(); let completed 0; const total 3; function onAllDone() { console.log(所有文件写入完成开始合并压缩); // 执行合并逻辑 } writer.on(done, (file) { console.log(${file} 写入完成); if (completed total) onAllDone(); }); writer.writeAndNotify(log1.txt, data1); writer.writeAndNotify(log2.txt, data2); writer.writeAndNotify(log3.txt, data3);特点原始但可控适合无法使用 Promise 的旧环境或需要细粒度事件监听时使用。6. 流式处理 —— 实时聚合多个传感器数据流场景物联网网关接收温度、湿度、气压三个传感器的实时数据流需要每收到一组三个传感器各一个值就计算平均值并推送。const { fromEvent, merge, bufferCount } require(rxjs); const { EventEmitter } require(events); const sensorA new EventEmitter(); const sensorB new EventEmitter(); const sensorC new EventEmitter(); // 模拟每秒推送一次数据 setInterval(() sensorA.emit(data, Math.random() * 30), 1000); setInterval(() sensorB.emit(data, Math.random() * 60), 1000); setInterval(() sensorC.emit(data, Math.random() * 10), 1000); // 将 EventEmitter 转为 Observable const streamA fromEvent(sensorA, data); const streamB fromEvent(sensorB, data); const streamC fromEvent(sensorC, data); // 合并并每收到3个值各一个计算一次平均值 merge(streamA, streamB, streamC) .pipe(bufferCount(3)) .subscribe(values { const avg values.reduce((a, b) a b, 0) / values.length; console.log(实时平均传感器值: ${avg.toFixed(2)}); });特点适合结果逐步产生、需要实时响应的场景。RxJS 提供了强大的组合能力。7. 队列控制并发 —— 限制同时上传文件的数量场景用户一次选择了 100 个文件上传到云存储必须控制同时上传的并发数为 5避免网络拥塞和服务器压力过大。const pLimit require(p-limit); const fs require(fs).promises; const path require(path); async function uploadFile(filePath) { console.log(开始上传 ${path.basename(filePath)}); await new Promise(r setTimeout(r, 1000)); // 模拟上传 console.log(完成上传 ${path.basename(filePath)}); return filePath; } async function uploadAll(filePaths) { const limit pLimit(5); // 最多5个并发 const tasks filePaths.map(filePath limit(() uploadFile(filePath)) ); const results await Promise.all(tasks); console.log(全部上传完成共 ${results.length} 个文件); } // 生成100个测试文件路径 const files Array.from({ length: 100 }, (_, i) /tmp/file${i}.txt); uploadAll(files);特点既保证并发效率又避免资源耗尽。配合Promise.all可以等待所有任务完成。总结一张表帮你快速选择场景推荐方案所有任务必须全部成功结果一起使用Promise.all容忍部分失败但需要知道每个任务的状态Promise.allSettled只取最快结果如超时、多源竞速Promise.race只要有一个成功即可忽略失败Promise.any旧项目回调风格或需要细粒度控制事件计数器 /EventEmitter结果流式输出、复杂组合如传感器数据RxJS / 异步迭代器大量任务且需控制并发数量队列 p-limit在实际项目中90% 的异步协作需求都可以用Promise.all和Promise.allSettled解决。对于更复杂的场景再考虑流式处理或队列控制。掌握这些模式你的 Node.js 异步编程能力将更上一层楼。

更多文章