最近参加了某条的前端面试,其中一面手写的一道实现有并行限制的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、2两个任务开始执行
- 500ms时,2任务执行完毕,输出2,任务3开始执行
- 800ms时,3任务执行完毕,输出3,任务4开始执行
- 1000ms时,1任务执行完毕,输出1,此时只剩下4任务在执行
- 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个数runCOunts
,add
操作函数就是往队列中插入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()
输出:
