某条一面异步题解析
前言
这道面试题论在当时我是写不出来的,当时自吹熟悉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
// 1200ms时,4完成,输出4
运行过程
@左值为剩余时间,右值为输出内容
执行队列(最大两个) | 等待队列 | 行为 | 执行内容 |
---|---|---|---|
1000@1 | 执行队列未满,直接进入执行队列 | addTask(1000, ‘1’) | |
1000@1、500@2 | 执行队列未满,直接进入执行队列 | addTask(500, ‘2’) | |
1000@1、500@2 | 300@3 | 执行队列已满,加入到等待队列 | addTask(300, ‘3’) |
1000@1、500@2 | 300@3、400@4 | 执行队列已满,加入到等待队列 | addTask(400, ‘4’) |
500@1、300@3 | 400@4 | 500@2执行完成输出2,1000@1消耗掉500,从等待队列按序加入到执行队列 | |
200@1、400@4 | 300@3执行完成输出3,500@1消耗掉300,从等待队列按序加入到执行队列 | ||
200@4 | 200@1执行完成输出1 | ||
200@4执行完成输出4 |
思路
注意add方法里面传入的是函数并返回Promise,这是难点,很多人都是改题,我见过拿getter、setter写的,我觉得跟题目要考的主旨不同。
前两个很好处理,直接判断执行队列中是否满员,未满直接进队
第三个及以后则需要判断前两者是否resolve,注意这里前两者和前两个的概念不同(由于是一层抽象,这里举例说明:目前处于第三个,那么前两者的前者指第一个到第一个,后者指第二个;目前处于第四个,那么前两者的前者指第一个到第二个,后者指第三个;以此类推),resolve后从等待队列按顺序加入到执行队列。
说下原因,有两种情况。前者先完成,也就是集合中的任务全部执行完成,那么后者一定会进入执行(未完成),那么执行队列中一定会剩下一个位置;后者先完成,这个没什么可说的,后者完成后一定会剩下一个位置。
代码
class Scheduler {
constructor() {
this.list=[] //promise list
this.cur=0 //current position
this.max=2
}
add(promiseCreator) {
let temp=null;
if(this.cur < this.max) {
temp=promiseCreator();
}else {
let arr=this.list.slice(0,this.cur-1);
let all=Promise.all(arr);
temp=Promise.race([all,this.list[this.cur-1]])
.then(() => {
return promiseCreator();
});
}
this.list.push(temp);
this.cur++;
return temp;
}
}
缺点与不足
无法复用
如果调整为执行队列最大个数为3或以上,则需要判断前n者中是否有resolve
容易内存爆炸
list一直保存着primise,无论resolve还是pedding
异常处理
最后
如果有小伙伴还有别的思路或者对缺点有思路的话欢迎评论