總結面試中 promise 相關題目的套路

Promise 做爲當下主流的異步解決方案,在工做中和麪試中經常出現,尤爲是在面試中,會弄個場景讓你手寫代碼,這裏給你們介紹五道比較有表明性的題目,以便熟悉一些套路。javascript

promise 簡單介紹

先簡單介紹下 Promisejava

Promise 對象用於表示一個異步操做的最終完成 (或失敗), 及其結果值。能夠爲異步操做的成功和失敗綁定執行函數,讓異步方法能夠像同步方法同樣返回值,但當即返回的是一個能表明將來可能出現結果的Promise對象。面試

Promise 對象有三種狀態:api

  • pending: 初始狀態,既不是成功,也不是失敗狀態。
  • fulfilled: 意味着操做成功完成。
  • rejected: 意味着操做失敗。

Promise 的使用和提供的靜態方法:數組

  • new Promise( function(resolve, reject) {...} /* executor */ ); :返回 Promise 對象
  • Promise.all(iterable) :iterable參數對象裏全部的promise對象都成功的時候纔會觸發成功,若一個失敗,則當即觸發返回Promise對象的失敗
  • Promise.race(iterable):iterable參數中的一個成功或者失敗都會當即觸發返回對象的成功和失敗
  • Promise.reject(reason):返回一個狀態爲失敗的Promise對象
  • Promise.resolve(value):返回一個狀態由value給定的Promise對象,一般用於將一個值以Promise的方式使用。

下面開始看題promise

題一

與js事件循環結合出題,以下,寫出執行結果併發

console.log('script start')
async function async1() {
    await async2()
    console.log('async1 end')
}
async function async2() {console.log('async2 end')}
async1()
setTimeout(function () {console.log('setTimeout')}, 0)
new Promise(resolve => {
    console.log('Promise')
    resolve()
}).then(function () {
        console.log('promise1')
    }).then(function () {
        console.log('promise2')
    })
console.log('script end')
// 結果以下
// script start
// async2 end
// Promise
// script end
// async1 end
// promise1
// promise2
// setTimeout
複製代碼

掌握事件循環機制和明白 Promise.then() 屬於微隊列,這一類的題目就都是一個套路。dom

題二

實現以下調用,lazyMan('xxx').sleep(1000).eat('333').sleepFirst(2000) sleepFirst 最早執行。異步

這題考察如何組合多個 Promise 和鏈式調用。async

能夠用數組將 sleep eat 等函數暫存,同時爲了能鏈式調用,因此每一個函數需返回 Promise 對象。那麼何時執行數組中的函數呢?

根據事件循環機制,咱們用 setTimeout 來執行數組中的方法,在定時器的回調函數中相關的事件已經添加到數組中了,鏈式執行數組中方法前,須要有一個構建一個 Promise 對象來執行 then 方法,能夠經過 Promise.resolve() 返回一個 Promise 對象。

function lazyMan(name) {
    this.task = [];
    this.task.push(() => {
        return new Promise(res => {
            console.log('name:' + name);res()
        })
    })
    let run = () => {
        let sequence = Promise.resolve()
        for (const func of this.task) {
            sequence = sequence.then(()=>func())
        }
    }
    setTimeout(() => {run()}, 0)
    this.eat = (str) => {
        this.task.push(() => {
            return new (res => {
                console.log('eat:' + str);res()
            })
        })
        return this;
    }
    this.sleep = (time) => {
        this.task.push(() => {
            return new Promise(res => {
                setTimeout(() => {
                    console.log(`Wake up after ` + time);res()
                }, time)
            })
        })
        return this;
    }
    this.sleepFirst = (time) => {
        this.task.unshift(() => {
            return new Promise(res => {
                setTimeout(() => {
                    console.log(`sleepFirst up after ` + time);res()
                }, time)
            })
        })
        return this;
    }
    return this;
}
複製代碼

題三

任務隊列可不斷的添加異步任務(異步任務都是Promise),但只能同時處理5個任務,5個一組執行完成後才能執行下一組,任務隊列爲空時暫停執行,當有新任務加入則自動執行。

class RunQune{
    constructor(){
        this.list = []; // 任務隊列
        this.target = 5; // 併發數量
        this.flag = false; // 任務執行狀態
        this.time = Date.now()
    }
    async sleep(time){
        return new Promise(res=>setTimeout(res,time))
    }
    // 執行任務
    async run(){
        while(this.list.length>0){
            this.flag = true;
            let runList = this.list.splice(0,this.target);
            this.time = Date.now()
            await this.runItem(runList)
            await this.sleep(300) // 模擬執行時間
        }
        this.flag = false;
    }
    async runItem(list){
        return new Promise((res)=>{
            while(list.length>0){
                const fn = list.shift();
                fn().then().finally(()=>{
                    if(list.length === 0){
                        res()
                    }
                })
            }
        })
    }
    // 添加任務
    push(task){
        this.list.push(...task);
        !this.flag && this.run()
    }
}
複製代碼

這題還能夠進一步發散,不須要等待一組完成在執行下一組,只要併發量沒有滿,就能夠加入新的任務執行,實現的思路沒太大變化,在 finally 中改成新增任務。

題四

指望id按順序打印 0 1 2 3 4 ,且只能修改 start 函數。

function start(id) {
    execute(id)
}
for (let i = 0; i < 5; i++) {
    start(i);
}
function sleep() {
    const duration = Math.floor(Math.random() * 500);
    return new Promise(resolve => setTimeout(resolve, duration));
}
function execute(id) {
    return sleep().then(() => {
        console.log("id", id);
    });
}
複製代碼

id 的打印是個異步事件,在 setTimeout 回調執行,按照上面的代碼,誰的倒計時先結束,id就先打印,那麼想要id按順序打印,就須要將多個異步事件同步執行,promise 的鏈式調用能夠派上用場。代碼以下

function start(id) {
    // execute(id)
    // 第一種:promise 鏈式調用,execute 函數返回的就是 promise ,因此能夠利用這一點,經過 promise.then 依次執行下一個打印
    this.promise = this.promise ? this.promise.then(()=>execute(id)) : execute(id)

    // 第二種:先用數組存儲異步函數,利用事件循環的下一個階段,即 setTimeout 的回調函數中執行 promise 的鏈式調用,這方法本質上和第一種是同樣的
    this.list = this.list ? this.list : []
    this.list.push(() => execute(id))
    this.t;
    if (this.t) clearTimeout(this.t)
    this.t = setTimeout(() => {
        this.list.reduce((re, fn) => re.then(() => fn()), Promise.resolve())
    })

    // 第三種:數組存儲id的值,在經過 await 異步執行 execute 函數
    this.list = this.list ? this.list : []
    this.list.push(id)
    clearTimeout(this.t)
    this.t = setTimeout(async () => {
        let _id = this.list.shift()
        while (_id !== undefined) {
            await execute(_id);
            _id = this.list.shift()
        }
    })
}
複製代碼

題五

手撕源碼系列,來手寫一個Promise,在動手前須要先了解 Promise/A+ 規範,列舉關鍵部分的規範,詳細規範可見文末連接

  1. Promise 的狀態:一個 Promise 的當前狀態必須爲如下三種狀態中的一種:等待態(Pending)、執行態(Fulfilled)和拒絕態(Rejected)。
  2. 狀態遷移:等待態能夠遷移至執行態或者拒絕態;執行態和拒絕態不能遷移至其餘狀態,且必須有一個不可變的終值
  3. then 方法:一個 promise 必須提供一個 then 方法以訪問其當前值、終值和據因,then 方法能夠被同一個 promise 調用屢次。then 方法接收兩個參數 onFulfilled, onRejected,onFulfilled 和 onRejected 必須被做爲函數調用,且調用不可超過1次。 then 方法需返回 Promise 對象

根據這三點我實現了一個簡化版的 Promise

function MPromise(executor) {
    this.status = 'pending'; // pending , fulfilled , rejected 
    this.data = '' // 當前promise的值,主要用於 then 方法中的 fulfilled , rejected 兩種狀態的處理
    this.resolveFuncList = []; // 使用數組的緣由是,一個promise能夠同時執行多個 then 方法, 也就會同時存在多個then回調
    this.rejectFunc;
    const self = this;
    function resolve(value) {
        // 使用 setTimeout 實現異步
        setTimeout(() => {
            if (self.status === 'pending') {
                self.status = 'fulfilled';
                self.data = value;
                // 執行 resolve 函數
                self.resolveFuncList.forEach(func => {
                    func(value)
                });
            }
        })
    }

    function reject(reason) {
        setTimeout(() => {
            if (self.status === 'pending') {
                self.status = 'rejected';
                self.data = value;
                self.rejectFunc && self.rejectFunc(reason);
            }
        })
    }
    try {
        executor(resolve, reject)
    } catch (error) {
        reject(error)
    }
}

MPromise.prototype.then = function (onFulfilled, onRejected) {
    let promise2;
    // 區分不一樣狀態下的處理
    if (this.status === 'pending') {
        return promise2 = new MPromise((res, rej) => {
            this.resolveFuncList.push(function (value) {
                let x = onFulfilled(value);
                resolvePromise(promise2, x, res, rej)
            })

            this.rejectFunc = function (reason) {
                let x = onRejected(reason);
                resolvePromise(promise2, x, res, rej)
            }
        })
    }
    if (this.status === 'fulfilled') {
        return promise2 = new MPromise((res, rej) => {
            setTimeout(() => {
                let x = onFulfilled(this.data) // 輸出將上一次執行結果
                resolvePromise(promise2, x, res, rej)
            })
        })
    }
    if (this.status === 'rejected') {
        return promise2 = new MPromise((res, rej) => {
            setTimeout(() => {
                let x = onRejected(this.data)
                resolvePromise(promise2, x, res, rej)
            })
        })
    }
}

function resolvePromise(promise2, x, resolve, reject) {
    if (x instanceof MPromise) {
        if (x.status === 'pending') {
            x.then(value => {
                resolvePromise(promise2, value, resolve, reject)
            }, reason => {
                reject(reason)
            })
        } else {
            x.then(resolve, reject)
        }
    } else {
        resolve(x)
    }
}
複製代碼

有的由於時間有限,會讓手寫 Promise 的 api,如下兩個就經常被問到

實現 Promise.all

/** * Promise.all Promise進行並行處理 * 參數: promise對象組成的數組做爲參數 * 返回值: 返回一個Promise實例 * 當這個數組裏的全部promise對象所有進入FulFilled狀態的時候,纔會resolve。 */
Promise.all = function(promises) {
    return new Promise((resolve, reject) => {
        let values = []
        let count = 0
        promises.forEach((promise, index) => {
            promise.then(value => {
                console.log('value:', value, 'index:', index)
                values[index] = value
                count++
                if (count === promises.length) {
                    resolve(values)
                }
            }, reject)
        })
    })
}
複製代碼

實現 Promise.rase

/** * Promise.race * 參數: 接收 promise對象組成的數組做爲參數 * 返回值: 返回一個Promise實例 * 只要有一個promise對象進入 FulFilled 或者 Rejected 狀態的話,就會繼續進行後面的處理(取決於哪個更快) */
Promise.race = function(promises) {
    return new Promise((resolve, reject) => {
        promises.forEach((promise) => {
            promise.then(resolve, reject);
        });
    });
}
複製代碼

小結

promise 的騷操做仍是很是多的,歡迎小夥伴在評論區一塊兒分享大家遇到題(tao)目(lu)。

參考文章

www.ituring.com.cn/article/665…

promisesaplus.com/

相關文章
相關標籤/搜索