如何实现N个JavaScript异步函数调用保持M个并发执行?
控制异步函数固定并发数(2个)的实现方案
你想用Promise.any()实现这个逻辑走不通很正常——Promise.any()的设计目标是等待第一个成功完成的Promise,它没法帮你跟踪每个任务的完成状态,也做不到在某个任务结束后自动启动下一个任务来维持固定并发数。
下面是直接可用的实现方案,完全符合你要求的逻辑:初始启动2个任务,任意一个完成后立刻启动下一个,直到所有任务都被触发,最后等待剩余的任务全部结束,全程并发数不超过2。
核心实现代码
async function runWithFixedConcurrency(taskFn, totalTasks, maxConcurrency = 2) { const runningPromises = []; let currentTaskIdx = 0; // 先启动初始的maxConcurrency个任务 while (currentTaskIdx < maxConcurrency && currentTaskIdx < totalTasks) { const task = launchNextTask(); runningPromises.push(task); } // 等待所有任务执行完毕 await Promise.all(runningPromises); // 封装启动单个任务的逻辑,包含完成后的后续处理 function launchNextTask() { const taskId = currentTaskIdx++; return taskFn(taskId).finally(() => { // 任务完成后,从运行队列中移除自己 const idx = runningPromises.indexOf(task); if (idx !== -1) runningPromises.splice(idx, 1); // 如果还有未启动的任务,立刻启动下一个 if (currentTaskIdx < totalTasks) { const nextTask = launchNextTask(); runningPromises.push(nextTask); } }); } }
代码逻辑说明
- 初始化并发任务:先启动前2个任务,把它们加入
runningPromises队列跟踪。 - 任务完成后的自动补位:每个任务的
finally回调会在任务结束(不管成功还是失败)时触发——先把自己从运行队列中移除,然后检查是否还有未启动的任务,如果有就立刻启动新任务并加入队列,保证始终维持2个并发。 - 等待全部完成:最后用
Promise.all()等待所有正在运行的任务结束,确保最后一批启动的任务也能执行完毕。
使用示例
假设你有一个模拟异步操作的函数(比如API请求、文件读写):
// 模拟异步任务,随机耗时1秒以内 async function demoAsyncTask(taskId) { console.log(`[${new Date().toLocaleTimeString()}] 启动任务 ${taskId}`); await new Promise(resolve => setTimeout(resolve, Math.random() * 1000)); console.log(`[${new Date().toLocaleTimeString()}] 完成任务 ${taskId}`); return taskId; } // 执行10个任务,保持2个并发 runWithFixedConcurrency(demoAsyncTask, 10);
运行这段代码你会看到,任何时刻最多只有2个任务在运行,一个任务结束后立刻启动下一个,直到所有任务都执行完成。
内容的提问来源于stack exchange,提问作者Howard May
相关产品推荐
相关产品推荐

