用固定大小执行槽位控制并发:通过promise队列与activecount计数器实现,任务超限则入pending队列,完成时自动触发下一个;支持取消、优先级、超时等进阶功能。

用一个固定大小的“执行槽位”来控制并发,任务来了先排队,有空槽才执行,这样既不压垮系统,又不会让任务无限堆积。
核心思路:用 Promise 队列 + 并发计数器
不靠第三方库也能实现。关键点有两个:一是维护一个运行中任务的数量(activeCount),二是用一个数组暂存等待中的任务(pending)。每次任务开始前检查是否还有空闲槽位;结束后主动触发下一个等待任务。
- 设定最大并发数(比如 max = 3)
- 每个任务包装成返回 Promise 的函数
- 执行时先判断 activeCount ,满足才真正运行,否则推入 pending 队列
- 每个任务完成(无论成功或失败)后,都调用 runNext() 尝试启动下一个等待任务
简易可运行代码示例
下面是一个轻量、无依赖的实现:
class AsyncQueue {
constructor(max = 3) {
this.max = max;
this.activeCount = 0;
this.pending = [];
}
add(task) {
return new Promise((resolve, reject) => {
this.pending.push({ task, resolve, reject });
this.runNext();
});
}
runNext() {
if (this.activeCount >= this.max || this.pending.length === 0) return;
const { task, resolve, reject } = this.pending.shift();
this.activeCount++;
Promise.resolve()
.then(() => task())
.then(res => {
resolve(res);
})
.catch(err => {
reject(err);
})
.finally(() => {
this.activeCount--;
this.runNext();
});
}
}
// 使用示例
const queue = new AsyncQueue(2);
const fetchWithDelay = (url, delay = 1000) =>
() => new Promise(r => setTimeout(() => r(`done: ${url}`), delay));
queue.add(fetchWithDelay('a', 800)).then(console.log);
queue.add(fetchWithDelay('b', 1200)).then(console.log);
queue.add(fetchWithDelay('c', 500)).then(console.log);
queue.add(fetchWithDelay('d', 900)).then(console.log);
它会确保任意时刻最多只有 2 个 fetchWithDelay 在执行,其余自动排队。
进阶考虑:取消、优先级、超时
生产环境可能需要更多控制能力:
- 取消任务:在 pending 中的任务可提供 cancel 方法;运行中的任务需内部支持 AbortController 或类似中断机制
- 优先级队列:把 pending 改成最小堆或按优先级排序的数组,高优任务插到前面
- 超时控制:add 时支持 timeout 选项,用 Promise.race 包裹 task 和一个延时 reject
- 错误重试:可在 .catch 后判断是否重试,并重新 add 回队列(注意限制次数)
现成方案参考(按需选用)
如果不想重复造轮子,这些库已被广泛验证:
- p-limit:极简,只做并发限制,返回一个 limit 函数,用法直观
- p-queue:功能完整,支持优先级、暂停/恢复、大小限制、超时等
- bull / agenda:适合需要持久化、失败重试、Web 管理界面的后台任务场景
小项目用 p-limit 足够;中大型应用建议从 p-queue 开始,扩展性更好。











