阅读 3089

某条一面异步题解析

前言

这道面试题论在当时我是写不出来的,当时自吹熟悉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

  • 异常处理

评论区的问题

基本上所有评论区的的思路都是差不多的。然后就是大部分人没有考虑抛出原promise的数据问题(学长发现的),如果实际应用的话,肯定要恰数据的嘛,虽然原题没有提到。

最后

如果有小伙伴还有别的思路或者对缺点有思路的话欢迎评论