Promise.all 本身不支持并发限制,所有传入的 Promise 会立即执行。在高并发场景下,建议引入 p-limit 库或手动实现异步队列来限制同时运行的任务数量,避免耗尽系统资源。
先说结论:原生 Promise.all 无法控制并发,必须通过外部队列机制拦截任务提交。
- 适合:IO 密集型任务批量处理,如文件读写、网络请求(Node.js 与浏览器均适用)
- 先看:任务失败是否会导致整体中断,决定用 all 还是 allSettled
- 建议:优先使用成熟库而非手写队列,减少边界条件错误
环境兼容与导入方式
p-limit 支持 Node.js 和现代浏览器环境。根据项目模块规范选择导入方式,避免语法报错:
// ESM 模式 (package.json 中 type: module 或 .mjs 文件)
import pLimit from 'p-limit';
// CommonJS 模式 (Node.js 默认)
const pLimit = require('p-limit');方案一:使用 p-limit 库(推荐)
这是最稳健的方案。注意任务必须是函数,不能是 Promise 实例,否则传入时就会立即执行。
1. 基础并发控制
const limit = pLimit(5); // 限制并发数为 5
const tasks = urls.map(url => limit(() => fetch(url)));
Promise.all(tasks).then(results => {
console.log('完成');
});2. 配合 Promise.allSettled 处理错误
避免单个任务失败导致整体中断,建议结合 allSettled 使用,并在任务内部捕获异常:
const tasks = urls.map(url =>
limit(async () => {
try {
const res = await fetch(url);
return { status: 'fulfilled', value: res };
} catch (err) {
return { status: 'rejected', reason: err };
}
})
);
const results = await Promise.allSettled(tasks);
// 后续处理 results 数组方案二:手动实现并发队列
如果不希望引入依赖,可以实现一个简单的并发控制器。核心逻辑是维护一个运行计数器和一个等待队列。
function createConcurrencyQueue(concurrency) {
let activeCount = 0;
const queue = [];
const next = () => {
activeCount--;
if (queue.length > 0) {
const task = queue.shift();
task();
}
};
return (taskFn) => {
return new Promise((resolve, reject) => {
const run = async () => {
activeCount++;
try {
const result = await taskFn();
resolve(result);
} catch (error) {
reject(error);
} finally {
next();
}
};
if (activeCount < concurrency) {
run();
} else {
queue.push(run);
}
});
};
}
// 使用示例
const queue = createConcurrencyQueue(5);
const tasks = urls.map(url => queue(() => fetch(url)));
Promise.all(tasks);自动化验证并发限制
不要仅靠肉眼观察日志,可以通过计数器断言验证并发是否超标。以下脚本可在 Node.js 环境中直接运行测试:
let maxConcurrent = 0;
let currentConcurrent = 0;
const limit = pLimit(5);
const tasks = Array.from({ length: 20 }, (_, i) =>
limit(async () => {
currentConcurrent++;
if (currentConcurrent > maxConcurrent) {
maxConcurrent = currentConcurrent;
}
// 模拟耗时
await new Promise(r => setTimeout(r, 100));
currentConcurrent--;
})
);
await Promise.all(tasks);
console.log(`设定并发:5,实际最大并发:${maxConcurrent}`);
if (maxConcurrent > 5) {
console.error('验证失败:并发数超出限制');
} else {
console.log('验证通过');
}常见坑与排查
- 错误处理:Promise.all 遇到单个 reject 会立即中断。若需忽略个别错误,需在任务内部 try-catch 或使用 Promise.allSettled。
- 内存泄漏:如果队列无限增长且消费速度慢,需确保任务有超时机制,避免队列堆积。
- 上下文丢失:异步队列可能导致 this 指向变化,建议使用箭头函数或显式 bind。
- 环境差异:浏览器环境需注意 CORS 和域名并发限制(通常 6 个),Node.js 需注意文件描述符限制。