某條一面非同步題解析

Weismann發表於2019-07-24

前言

這道面試題論在當時我是寫不出來的,當時自吹熟悉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的資料問題(學長發現的),如果實際應用的話,肯定要恰資料的嘛,雖然原題沒有提到。

最後

如果有小夥伴還有別的思路或者對缺點有思路的話歡迎評論

相關文章