手写异步任务调度器

目标:同时最多执行 limit 个异步任务,一个任务结束后立即补充下一个,并保持结果顺序与输入一致。

实现

任务必须使用函数而不是已经创建的 Promise。已经创建的 Promise 会立即执行,调度器无法控制它何时开始。

async function schedule(taskFactories, limit) {
  if (!Number.isInteger(limit) || limit <= 0) {
    throw new RangeError('limit must be a positive integer');
  }

  const results = new Array(taskFactories.length);
  let nextIndex = 0;

  async function worker() {
    while (true) {
      const currentIndex = nextIndex;
      nextIndex += 1;

      if (currentIndex >= taskFactories.length) return;
      results[currentIndex] = await taskFactories[currentIndex]();
    }
  }

  const workerCount = Math.min(limit, taskFactories.length);
  const workers = Array.from({ length: workerCount }, () => worker());

  await Promise.all(workers);
  return results;
}

使用示例

const delay = (value, milliseconds) => () =>
  new Promise((resolve) => {
    setTimeout(() => resolve(value), milliseconds);
  });

const results = await schedule([
  delay('A', 300),
  delay('B', 100),
  delay('C', 200),
  delay('D', 50),
], 2);

console.log(results); // ['A', 'B', 'C', 'D']

虽然 B 先完成,结果仍然按任务输入顺序保存。

错误和取消策略

当前实现是 fail-fast:任一任务失败,返回的 Promise 立即拒绝,但已经开始的任务不会自动停止。

若要收集全部结果,可以在 worker 内捕获异常并保存 { status, value/reason }。若要取消网络任务,可让任务工厂接收同一个 AbortSignal,调度器领取新任务前也要检查 signal.aborted

仅停止调度新任务不等于取消正在执行的任务;底层任务必须主动支持取消。

高频追问

  • 为什么传入 Promise 数组无法限制并发?
  • 一个任务失败后,已执行的任务会停止吗?
  • 如何加入超时、重试和优先级?
  • JavaScript 单线程为什么仍需要限制请求并发?