发布于2026-07-06 阅读(0)
扫一扫,手机访问
在Node.js的世界里,异步操作几乎无处不在,而如何优雅地控制并发——既要保证效率,又不能把系统搞崩——是每个开发者迟早要面对的问题。方法其实不少,关键是根据具体场景选对那个趁手的工具。

先来看看最常见的几种做法。
1. 使用async库
如果你还在用回调风格写异步代码,或者维护着老项目,async库是个很实用的选择。它提供了parallel、series、waterfall等函数,让我们能清晰地编排多个异步任务的执行顺序。比如用async.parallel来并发执行三个任务:
const async = require('async');
const task1 = (callback) => {
// 任务1的代码
callback(null, '结果1');
};
const task2 = (callback) => {
// 任务2的代码
callback(null, '结果2');
};
const task3 = (callback) => {
// 任务3的代码
callback(null, '结果3');
};
async.parallel([task1, task2, task3], (err, results) => {
if (err) {
console.error('发生错误:', err);
} else {
console.log('任务结果:', results);
}
});
这种方式的好处是直观、底层兼容性好,缺点是需要层层回调,代码一多就不好维护了。
2. 使用Promise.all
ES6之后,Promise成了异步编程的主流。Promise.all接受一个Promise数组,当所有Promise都resolve时,返回一个包含所有结果的数组;只要有一个reject,整体就立即失败。同样是三个任务:
const promise1 = new Promise((resolve, reject) => {
resolve('结果1');
});
const promise2 = new Promise((resolve, reject) => {
resolve('结果2');
});
const promise3 = new Promise((resolve, reject) => {
resolve('结果3');
});
Promise.all([promise1, promise2, promise3])
.then((results) => {
console.log('任务结果:', results);
})
.catch((error) => {
console.error('发生错误:', error);
});
相比回调,Promise的链式调用清晰很多,而且支持错误冒泡。不过要注意,Promise.all是“全有或全无”的——任何一个任务失败,其他任务的结果就丢了(虽然它们可能还在执行)。
3. 使用async/await
async/await本质上是Promise的语法糖,但写起来更像同步代码,可读性极佳。把上面的例子改成async/await:
async function runTasks() {
try {
const result1 = await promise1;
const result2 = await promise2;
const result3 = await promise3;
console.log('任务结果:', [result1, result2, result3]);
} catch (error) {
console.error('发生错误:', error);
}
}
runTasks();
注意:这里如果写成依次await,其实是串行执行,而不是并发。要实现真正的并发,通常需要先启动所有Promise(比如存入数组),再用await Promise.all(...)。这个细节容易被忽略,但很关键。
4. 使用信号量(Semaphore)
前面几种方法都是“全部并发”或“全部串行”,但在实际业务中,我们经常需要限制同时执行的任务数量——比如同时最多发5个网络请求,避免把下游服务打爆。这时候就需要信号量。
在Node.js中,bottleneck是一个很成熟的限流库。它的用法非常简洁:
const Bottleneck = require('bottleneck');
const limiter = new Bottleneck({
maxConcurrent: 2, // 同时最多执行2个任务
minTime: 100 // 每个任务之间至少间隔100毫秒
});
const task1 = () => { /* 任务1的代码 */ };
const task2 = () => { /* 任务2的代码 */ };
const task3 = () => { /* 任务3的代码 */ };
limiter.schedule(task1);
limiter.schedule(task2);
limiter.schedule(task3);
除了bottleneck,你还可以用async.queue、p-limit等方案,思路大同小异。信号量的核心价值在于控制资源水平,避免系统过载,这在生产环境中几乎是必备技能。
说到底,没有银弹。追求极致性能就上Promise.all并合理控制并发数;代码可维护性优先就用async/await配合限流;如果是遗留项目或需要精细控制任务依赖,async库依然是可靠的选择。根据业务场景灵活组合,才是最佳实践。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8