并发限制调度器

先理解原理和示例,再练习完整口述与追问。

输入任务函数;固定 worker 数;同步异常与异步拒绝;保序与失败策略。

限制并发需要输入任务函数,只有获得槽位才调用函数启动工作。维护下一个索引与运行中的数量,某项完成后释放槽位并继续调度;结果仍按原输入索引保存。错误策略可选择快速失败或收集全部,但必须说明失败后未启动任务是否继续。

并发数 2,先启动 A/B;B 完成后启动 C,A 完成后启动 D。记录任意时刻 active 不超过 2,同时 C 先完成也不改变结果数组顺序。用一个任务同步抛错、一个异步拒绝验证槽位释放与最终错误传播。

先 map 调用全部请求再交给限流器,工作已经开始,不能限住实际并发。取消、超时和重试也会影响槽位,需要统一定义。

任务必须延迟到调度器调用时才开始,所以传函数而不是已经启动的 Promise。我启动最多 limit 个 worker,每个完成后取下一任务。下面采用 allSettled 式策略,某项失败记录 reason 并继续,所有项结束后按输入顺序返回;这避免“某个请求失败后整个队列永久停住”的问题。若业务要求首错停止,需要另外定义停止派发和在途取消,不靠 Promise 拒绝自动完成。

js
async function mapLimit(tasks, limit) {
  if (!Array.isArray(tasks)) throw new TypeError('tasks 必须是数组')
  if (!Number.isInteger(limit) || limit <= 0) {
    throw new RangeError('limit 必须是正整数')
  }
  const queue = Array.from(tasks)
  if (queue.some(task => typeof task !== 'function')) {
    throw new TypeError('每个任务必须是函数')
  }
  const results = new Array(queue.length)
  let next = 0
  async function worker() {
    while (next < queue.length) {
      const index = next++
      try {
        const task = queue[index]
        results[index] = { status: 'fulfilled', value: await task() }
      } catch (reason) {
        results[index] = { status: 'rejected', reason }
      }
    }
  }
  await Promise.all(Array.from({ length: Math.min(limit, queue.length) }, worker))
  return results
}

示例:await mapLimit([()=>1,()=>Promise.reject('失败')],2) 返回一项 fulfilled、一项 rejected。空数组返回 [],非法 limit 或非函数任务拒绝。额外结果空间 O(n),worker 数为 O(min(n,limit));总体耗时与任务耗时分布有关。这个版本不内置取消与自动重试,也不会声称已停止服务端任务。