node.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > node.js > node.js 异步任务协作

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

作者:宝青书坊

文章介绍了7种Node.js中处理多个独立异步任务的方案,包括Promise.all、Promise.allSettled、Promise.race、Promise.any、事件计数器、流式处理和队列控制,每种方案都适用于特定场景,感兴趣的朋友跟随小编一起看看吧

多个独立异步任务,如何高效协作?从 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 异步编程能力将更上一层楼。

到此这篇关于Node.js 异步任务协作方案:7 种实用方案与真实项目案例的文章就介绍到这了,更多相关node.js 异步任务协作内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文