标准答案
- 并发控制限制的是任务启动数量,不是限制已经创建好的 Promise。输入最好是一组返回 Promise 的函数,因为函数还没调用前任务没有开始。
- 实现时维护三个核心状态:下一个要启动的下标、当前运行中的任务数量、结果数组。只要运行数量小于限制,就继续启动新任务。
- 每个任务完成后,把结果写回原下标位置,运行数量减一,再补启动下一个任务。这样可以保证最多同时运行 N 个,并且最终结果顺序和输入顺序一致。
- 错误策略要提前定清楚:可以一个任务失败就整体 reject,也可以像 allSettled 一样记录每个任务的 fulfilled/rejected 状态。不同业务场景需要不同策略。
题目解析
这里关注真实工程异步控制,比如批量请求、图片上传、文件处理。
已经创建的 Promise 往往已经开始执行,把它们传给调度器时,并发已经发生了。因此并发控制通常接收任务函数,而不是 Promise 实例。
结果顺序是容易忽略的边界。任务完成顺序通常不等于输入顺序,如果直接 push 结果,调用方拿到的顺序会不稳定。用原下标写入 results 可以保留顺序。
并发控制和 Promise.all 的职责不同。Promise.all 只组合结果,不负责限制任务启动数量;并发限制需要额外的队列调度逻辑。
代码示例
下面实现会限制同时运行的任务数量,并按输入顺序返回结果。
JavaScript
function runWithLimit(tasks, limit) {
return new Promise((resolve, reject) => {
const results = []
let nextIndex = 0
let running = 0
let finished = 0
function launch() {
if (finished === tasks.length) {
resolve(results)
return
}
while (running < limit && nextIndex < tasks.length) {
const current = nextIndex++
running += 1
Promise.resolve()
.then(() => tasks[current]())
.then((result) => {
results[current] = result
finished += 1
running -= 1
launch()
}, reject)
}
}
launch()
})
}常见误区
- 把一组已经启动的 Promise 传进去,误以为还能限制并发。
- 只控制启动数量,不在任务结束后继续补位。
- 结果按完成顺序返回,破坏调用方预期。