标准答案
- 并发控制接收的最好是任务函数数组,而不是已经创建好的 Promise 数组。
- 维护 nextIndex、running、finished 和 results。
- 只要 running 小于 limit,就继续启动下一个任务。
- 每个任务成功后按原始 index 写入结果,失败策略可以选择立即 reject 或记录错误后继续。
- 任务完成或失败后都要释放运行名额,否则队列会卡住。
题目解析
限制并发常见于批量上传、批量下载、批量接口补数据。它的目的不是让代码更复杂,而是避免瞬间打满浏览器连接数、后端限流或用户网络。
传入任务函数很重要。如果外部已经把 Promise 创建好了,请求可能已经全部开始,此时再限制只是在限制等待结果,不是在限制请求启动。
错误策略要提前讲清。手写题里可以采用快速失败;业务里也可能需要继续跑完剩余任务,再返回成功和失败明细。
代码示例
下面的实现限制最多同时运行 limit 个任务,并按输入顺序返回结果。
JavaScript
function runWithLimit(tasks, limit) {
return new Promise((resolve, reject) => {
const results = []
let nextIndex = 0
let running = 0
let finished = 0
if (limit < 1) {
reject(new RangeError('limit must be greater than 0'))
return
}
if (tasks.length === 0) {
resolve([])
return
}
function schedule() {
while (running < limit && nextIndex < tasks.length) {
const currentIndex = nextIndex++
running += 1
Promise.resolve()
.then(() => tasks[currentIndex]())
.then(value => {
results[currentIndex] = value
finished += 1
running -= 1
if (finished === tasks.length) {
resolve(results)
} else {
schedule()
}
}, reject)
}
}
schedule()
})
}常见误区
- 传入已经开始执行的 Promise,再说自己做了并发限制。
- 任务完成后没有 running -= 1,导致后续任务永远不会启动。
- 按完成顺序 push 结果,调用方无法对应原始任务。