跳到主要内容
← 返回文章列表
面试题-基础2026/07/02约 1 分钟读完

手写带并发限制的 Promise 调度器

【面试题】手写带并发限制的 Promise 调度器

手写带并发限制的 Promise 调度器
文章目录 6

手写带并发限制的 Promise 调度器

核心思路

用一个计数器追踪正在执行的任务数,用一个等待队列存放还没轮到的任务。每当有任务完成,就从队列里取下一个任务执行。

实现

class Scheduler {
  constructor(max) {
    this.max = max;        // 最大并发数
    this.running = 0;      // 当前正在执行的任务数
    this.queue = [];       // 等待队列
  }

  add(task) {
    return new Promise((resolve, reject) => {
      // 把任务包装一下,带上 resolve/reject
      const job = () => {
        task().then(resolve, reject).finally(() => {
          this.running--;
          this._runNext();
        });
      };

      if (this.running < this.max) {
        // 没达到并发上限,直接执行
        this.running++;
        job();
      } else {
        // 超过上限,放入等待队列
        this.queue.push(job);
      }
    });
  }

  _runNext() {
    if (this.queue.length > 0) {
      this.running++;
      const job = this.queue.shift();
      job();
    }
  }
}

使用示例

const scheduler = new Scheduler(2); // 最多同时跑 2 个

function timeout(id, ms) {
  return () => new Promise(resolve => {
    console.log(`任务 ${id} 开始`);
    setTimeout(() => {
      console.log(`任务 ${id} 完成`);
      resolve(id);
    }, ms);
  });
}

scheduler.add(timeout('A', 3000));  // 立即执行
scheduler.add(timeout('B', 1000));  // 立即执行(并发 2)
scheduler.add(timeout('C', 2000));  // 等 A 或 B 完成后执行
scheduler.add(timeout('D', 500));   // 继续排队

// 输出:
// 任务 A 开始
// 任务 B 开始
// 任务 B 完成     (B 先完成,C 开始)
// 任务 C 开始
// 任务 A 完成     (A 完成,D 开始)
// 任务 D 开始
// 任务 C 完成
// 任务 D 完成

实际应用场景

1. 图片批量上传

// 用户一次性选了 50 张图片,同时上传最多 3 个
const uploadScheduler = new Scheduler(3);

const results = await Promise.all(
  files.map(file => uploadScheduler.add(() => uploadFile(file)))
);

如果不限制并发,浏览器会同时发出 50 个请求,直接打满带宽,还可能触发服务端限流。

2. 爬虫/API 批量请求

// 爬取 B 站关注列表的 200 个 UP 主动态
const crawler = new Scheduler(3); // 最多 3 个并发

const dynamics = await Promise.all(
  uids.map(uid => crawler.add(() => fetchUserDynamics(uid)))
);

不限制并发的话,几百个请求瞬间发出,会被反爬风控(-412)封禁。

3. 页面图片懒加载的请求控制

// 首屏同时加载 30 张图片,限制同时最多 4 个
const imgLoader = new Scheduler(4);

images.forEach(img => {
  imgLoader.add(() => loadImage(img.dataset.src));
});

4. 单元测试并行执行控制

// 100 个测试用例,限制最多 5 个并行
const testRunner = new Scheduler(5);

const results = await Promise.all(
  testCases.map(tc => testRunner.add(() => runTest(tc)))
);

共同本质

这些场景的核心问题是相同的:

一次性抛出的任务太多,但系统(浏览器、服务器、带宽)承受能力有限,需要排队+限流。

不定并发数的 Promise.all 是"有多少同时来多少",加上调度器后就变成了"最多同时跑 n 个,其余排队等待"。

评论

0 条

评论会在审核后展示,邮箱不会公开。

正在确认登录状态

5000 / 5000