在现代JavaScript开发中,异步操作是非常常见的场景,当我们需要同时处理多个异步任务时,如果不做并发控制,可能会导致资源占用过高、请求过载等问题。Promise作为JavaScript处理异步的核心方案,提供了多种并发控制的方式。
Promise原生并发方法对比
JavaScript内置的Promise对象提供了几个用于并发处理异步任务的方法,不同方法的适用场景有明显差异,下面我们来逐一分析。
Promise.all
Promise.all接收一个Promise数组,当所有Promise都成功时返回所有结果组成的数组,只要有一个Promise失败就直接进入catch回调,适合需要所有任务都成功才能继续后续逻辑的场景。
// 模拟异步请求函数
function asyncTask(id, delay, success = true) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (success) {
resolve(`任务${id}完成,耗时${delay}ms`);
} else {
reject(`任务${id}失败`);
}
}, delay);
});
}
// 使用Promise.all并发执行三个任务
Promise.all([
asyncTask(1, 1000),
asyncTask(2, 1500),
asyncTask(3, 800)
]).then(results => {
console.log('所有任务都成功:', results);
}).catch(err => {
console.log('有任务失败:', err);
});
Promise.allSettled
Promise.allSettled同样接收Promise数组,它会等待所有Promise都完成(不管成功还是失败),最终返回一个包含每个任务状态的对象数组,适合需要统计所有任务执行结果的场景,不需要因为单个任务失败就中断整体流程。
Promise.allSettled([
asyncTask(1, 1000, true),
asyncTask(2, 1500, false),
asyncTask(3, 800, true)
]).then(results => {
console.log('所有任务执行完毕,结果如下:');
results.forEach(item => {
if (item.status === 'fulfilled') {
console.log(`成功: ${item.value}`);
} else {
console.log(`失败: ${item.reason}`);
}
});
});
Promise.race
Promise.race返回第一个完成(不管是成功还是失败)的Promise结果,适合需要设置超时逻辑或者只需要获取最快返回结果的场景。
// 模拟超时逻辑
function withTimeout(promise, timeout) {
return Promise.race([
promise,
new Promise((_, reject) => {
setTimeout(() => {
reject('请求超时');
}, timeout);
})
]);
}
const task = asyncTask(1, 2000, true);
withTimeout(task, 1500).then(res => {
console.log(res);
}).catch(err => {
console.log(err); // 输出: 请求超时
});
自定义并发控制队列实现
上面的原生方法要么是所有任务同时发起,要么是只取最快结果,无法满足限制最大并发数量的需求。比如我们需要同时请求100个接口,但是最多只能同时发起5个请求,避免服务端压力过大,这时候就需要手动实现并发控制队列。
实现思路
- 维护一个任务队列,存放所有需要执行的异步任务
- 维护一个正在执行的任务数组,记录当前正在运行的任务
- 设置最大并发数,初始时取出最大并发数个任务执行
- 当有任务执行完成时,从正在执行数组中移除该任务,再从任务队列中取出新任务执行
- 直到所有任务都执行完毕,返回所有结果
完整代码实现
/**
* Promise并发控制函数
* @param {Array<Function>} tasks 异步任务数组,每个元素是返回Promise的函数
* @param {number} maxConcurrency 最大并发数
* @returns {Promise<Array>} 所有任务的结果数组
*/
function promiseConcurrentControl(tasks, maxConcurrency) {
return new Promise((resolve) => {
// 校验参数
if (!Array.isArray(tasks) || tasks.length === 0) {
resolve([]);
return;
}
if (maxConcurrency <= 0) {
maxConcurrency = 1;
}
const results = []; // 存储所有任务结果
const executing = []; // 存储正在执行的任务
let currentIndex = 0; // 当前任务索引
// 执行单个任务的函数
function runTask() {
// 如果所有任务都执行完了,判断是否需要结束
if (currentIndex >= tasks.length && executing.length === 0) {
resolve(results);
return;
}
// 如果还有任务没执行,且当前执行中的任务数小于最大并发数,就继续执行新任务
while (currentIndex < tasks.length && executing.length < maxConcurrency) {
const taskIndex = currentIndex;
const task = tasks[currentIndex];
currentIndex++;
// 执行任务,保存promise
const taskPromise = task().then(res => {
results[taskIndex] = {
status: 'fulfilled',
value: res
};
}).catch(err => {
results[taskIndex] = {
status: 'rejected',
reason: err
};
}).finally(() => {
// 任务完成后,从正在执行的数组中移除
const execIndex = executing.indexOf(taskPromise);
if (execIndex > -1) {
executing.splice(execIndex, 1);
}
// 继续执行下一个任务
runTask();
});
executing.push(taskPromise);
}
}
// 启动执行
runTask();
});
}
// 测试自定义并发控制函数
const testTasks = [];
for (let i = 0; i < 10; i++) {
testTasks.push(() => asyncTask(i + 1, Math.random() * 1000 + 500));
}
promiseConcurrentControl(testTasks, 3).then(res => {
console.log('所有任务执行完毕,结果:');
res.forEach(item => {
if (item.status === 'fulfilled') {
console.log(`成功: ${item.value}`);
} else {
console.log(`失败: ${item.reason}`);
}
});
});
实际场景应用建议
在实际开发中,我们可以根据需求选择合适的并发控制方案:
- 如果多个异步任务相互依赖,必须全部成功才能继续,优先使用
Promise.all - 如果需要获取所有任务的执行结果,不管成功失败,使用
Promise.allSettled - 如果需要设置请求超时或者只需要最快返回的结果,使用
Promise.race - 如果需要限制同时执行的异步任务数量,比如批量上传文件、批量请求接口,使用自定义的并发控制队列
合理控制Promise的并发数量,可以有效避免浏览器或者Node.js环境的资源耗尽问题,提升应用的健壮性和用户体验。
JavaScriptPromise并发控制异步编程修改时间:2026-07-17 02:51:39