某条高频面试原题:实现有并行限制的Promise调度器

8,632 阅读3分钟

最近参加了某条的前端面试,其中一面手写的一道实现有并行限制的Promise调度器问题倒是第一次见,写的时候花费了不少时间,紧接着二面又让手写一道类似的限制并行Promise的题目(大致思路一样),下来之后特意完善、总结了这道题的解法。

题目详情

JS实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个。完善下面代码的Scheduler类,使以下程序能够正常输出:

class Scheduler {
  add(promiseCreator) { ... }
  // ...
}
   
const timeout = time => new Promise(resolve => {
  setTimeout(resolve, time);
})
  
const scheduler = new Scheduler();
  
const addTask = (time,order) => {
  scheduler.add(() => timeout(time).then(()=>console.log(order)))
}

addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');

// output: 2 3 1 4

整个的完整执行流程:

  1. 其实1、2两个任务开始执行
  2. 500ms时,2任务执行完毕,输出2,任务3开始执行
  3. 800ms时,3任务执行完毕,输出3,任务4开始执行
  4. 1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
  5. 1200ms时,4任务执行完毕,输出4

解题思路

可以看到,最多时存在两个并行的Promise,并且一个Promise执行完成之后,执行新的Promise,并且新执行的Promise不会影响到另一个正在执行的Promise。

既然如此的话,就不能使用Promise.all()Promise.race()这两个API了,Promise.all()会等待所有Promise完成,Promise.race()只会执行一个Promise。

其实从Promise依序进行执行,可以使用队列先进先出的特性,add操作知识每次用队列中插入Promise Creator,判断当前执行数量是否小于2,如果小于2就从队列中弹出Promise Creator执行并给执行的Promise绑定then函数,then函数被调用就说明当前Promise已经执行完成,重复当前操作,可以看出是一个递归的操作。

实际上手

有了思路,就可以开始写代码补充这个类了。

首先定义了存储Promise Creator的数组queue,最大并行个数maxCount,当前执行的Promise个数runCOuntsadd操作函数就是往队列中插入Promise Generator函数。

class Scheduler {
  constructor() {
    this.queue = [];
    this.maxCount = 2;
    this.runCounts = 0;
  }
  add(promiseCreator) {
    this.queue.push(promiseCreator);
  }
}

接来下就是request函数:每次从队列中取出Promise Generator并执行,此Promise执行完成之后应该调用递归调用request函数做到执行下一个Promise。

request() {
    if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
      return;
    }
    this.runCounts++;

    this.queue.shift()().then(() => {
      this.runCounts--;
      this.request();
    });
}

当然,还差最后一个启动函数,需要将2个Promise启动起来:

taskStart() {
    for (let i = 0; i < this.maxCount; i++) {
      this.request();
    }
}

完整代码:

class Scheduler {
  constructor() {
    this.queue = [];
    this.maxCount = 2;
    this.runCounts = 0;
  }
  add(promiseCreator) {
    this.queue.push(promiseCreator);
  }
  taskStart() {
    for (let i = 0; i < this.maxCount; i++) {
      this.request();
    }
  }
  request() {
    if (!this.queue || !this.queue.length || this.runCounts >= this.maxCount) {
      return;
    }
    this.runCounts++;

    this.queue.shift()().then(() => {
      this.runCounts--;
      this.request();
    });
  }
}
   
const timeout = time => new Promise(resolve => {
  setTimeout(resolve, time);
})
  
const scheduler = new Scheduler();
  
const addTask = (time,order) => {
  scheduler.add(() => timeout(time).then(()=>console.log(order)))
}
  
  
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
  
scheduler.taskStart()

输出: