JavaScript并发池核心是控制同时执行的Promise数量不超过上限,通过运行中与待执行队列调度,完成即补新任务,不阻塞主线程;提供concurrencyPool和concurrencyPoolAsync两种实现方式。

在 JavaScript 中,用 async/await 实现异步并发池,核心是控制同时执行的 Promise 数量不超过设定上限,避免资源耗尽或接口限流。关键不在于“等所有完成”,而是在有空位时立即启动新任务。
并发池的基本原理
维护一个“运行中任务队列”和一个“待执行任务队列”。每次最多允许 n 个 Promise 同时 await;当某个完成,就从待执行队列取下一个补上。整个过程不阻塞主线程,也不依赖定时器或手动 Promise.race 轮询。
实现一个通用并发池函数
下面是一个简洁、可复用的 concurrencyPool 函数:
function concurrencyPool(tasks, limit) {
const results = [];
let running = 0;
let index = 0;
return new Promise(resolve => {
function runNext() {
if (index >= tasks.length && running === 0) {
return resolve(results);
}
while (running < limit && index < tasks.length) {
const task = tasks[index++];
running++;
task()
.then(result => {
results.push({ status: 'fulfilled', value: result });
})
.catch(error => {
results.push({ status: 'rejected', reason: error });
})
.finally(() => {
running--;
runNext();
});
}
}
runNext();
});
}
使用示例:
立即学习“Java免费学习笔记(深入)”;
const urls = ['/api/1', '/api/2', '/api/3', '/api/4']; const requests = urls.map(url => () => fetch(url).then(r => r.json()) ); concurrencyPool(requests, 2).then(console.log); // 最多 2 个 fetch 并发
更现代的写法:基于 Promise.allSettled + 控制调度
如果希望代码更易读、更符合 async/await 风格(比如想用 await 写任务),可以用“批处理 + 递归调度”方式:
async function concurrencyPoolAsync(tasks, limit) {
const results = [];
const executing = [];
for (const task of tasks) {
const promise = Promise.resolve().then(() => task());
results.push(promise);
if (limit > 0) {
const e = promise.finally(() => executing.splice(executing.indexOf(e), 1));
executing.push(e);
if (executing.length >= limit) {
await Promise.race(executing);
}
}
}
return Promise.allSettled(results);
}
说明:
-
Promise.resolve().then(...)确保任务被异步调度,避免同步执行压栈 - 用
executing数组跟踪当前运行中的 Promise,并在finally中清理 -
Promise.race(executing)等任意一个完成就继续派发,保持池子“常满” - 最终统一用
Promise.allSettled收集全部结果(含成功/失败)
注意事项与常见坑
- 不要直接 await 所有任务数组:那样会一次性触发全部,失去限流意义
-
错误不能被外层 try/catch 捕获:建议统一用
allSettled或在每个 task 内部catch - limit 设为 0 或负数时需兜底:可视为无限制,但通常应校验并抛错
- 任务函数必须返回 Promise:否则无法被 await 或加入执行队列
不复杂但容易忽略:并发池不是“节流”也不是“防抖”,它管的是“正在跑几个”,而不是“多久跑一次”。理解这点,就能灵活适配爬虫、批量上传、接口聚合等场景。


















