天天看點

從零開始 - 40行代碼實作一個簡單Promise函數

一個遵循​​PromiseA+規範​​的函數,個人認為解決了callback異步回調地獄的問題,注意是callback方式的回調地獄,promise本身也可以存在回調地獄,需配合ES7特性async、await才能做到完全解決回調地獄。

Promise主要特點

  1. Promise 會有三種狀态,「進⾏中」「已完成」和「已拒絕」,進⾏中狀态可以更改為已完成或已拒絕,已經更改過狀态後⽆法繼續更改(例如從已完成改為已拒絕)。
  2. Promise 構造之後需要傳⼊⼀個函數,它接受兩個參數,執⾏第⼀個參數之後就會改變目前 promise 為「已完成」狀态,執⾏ 第⼆個參數之後就會變為「已拒絕」狀态。
  3. 通過​

    ​.then​

    ​ ⽅法,即可在上⼀個 promise 達到已完成 時繼續執⾏下⼀個函數或 promise。同時通過 resolve 或 reject 時傳⼊參數,即可給下⼀個函數或 promise 傳⼊初始值。
  4. 已拒絕的 promise,後續可以通過 .catch ⽅法或是 .then ⽅法的第⼆個參數或是 try catch 進⾏捕獲。

根據特性梳理結構

class simplePromise {
    constructor(handleFn) { // 構造時傳入一個函數
        this.status = 'pending' // 有狀态控制
        handleFn(resolve, reject) // 該函數接收兩個參數
    }
    then() { // 它有一個then方法
        return new simplePromise(() => { // 傳回的是promise執行個體then才得以串聯起來
            if (this.status === 'pending') { // 狀态一旦改變就不可修改
                // TODO
            }
        })
    }
    // 一些常用的靜态方法
    catch() {}
    resolve() {}
    reject() {}
}      

實作resolve

這是Promise的第一個關鍵點,主要在于邏輯的判斷,如何将then串聯起來,以及最後收集依賴

class SimplePromise {
    constructor(handleFn) {
        this.status = 'pending' // 标記狀态
        this.fulfilledList = [] // 任務隊列
        handleFn(params { })
    }
    then(onFulfilled, onRejected) { // then()接收兩個函數
        // 傳回一個Promise
        return new SimplePromise((onNextFulfilled, onNextRejected) => {
            function finalFn(params) {
                // 如果不是函數,跳過執行下一步
                if (typeof onFulfilled !== 'function') {
                    onNextFulfilled(params)
                } else {
                    // 如果是函數,先接收其傳回結果
                    const res = onFulfilled(params)
                    // 判斷是否為Promise,也可以判斷有沒有then方法typeof res.then === 'function'
                    if (res && res instanceof SimplePromise) {
                        res.then(onNextFulfilled) // 繼續執行Promise
                    } else {
                        onNextFulfilled(res) // 不是Promise繼續執行下一步
                    }
                }
            }
            if (this.status === 'pending') {
                this.fulfilledList.push(finalFn) // 收集依賴      

第二個關鍵點在于,使用者傳入的函數,在構造時會立即執行,但收集依賴是在then方法中,是以需要将傳入的函數放到異步隊列中去執行

class SimplePromise {
    constructor(handleFn) {
        this.status = 'pending' // 标記狀态
        this.fulfilledList = [] // 任務隊列
        handleFn((val) => {
            setTimeout(() => { // 抛進異步隊列,保證不會立即執行
                if (this.status !== 'pending') { return } // 狀态一旦确定再無法變更
                this.status = 'fulfilled' // 執行就改變狀态
                this.fulfilledList.forEach(fn fn(val))// 開始執行函數
                this.fulfilledList = [] // 釋放
            }, 0)
        })
    }
    then(onFulfilled, onRejected) { ..... }
    catch() { }
    resolve() { }
    reject() { }
}      
  • 接下來将reject也實作進去,handleFn為使用者傳入的函數,其接收兩個參數resolve和reject,我們分别執行了兩個隊列裡收集的任務。
  • 而then中則建立了一個工廠函數,用于生成兩種收集依賴的方法。

完整代碼

class SimplePromise {
    constructor(handleFn) {
        this.status = 'pending' // 标記狀态
        this.fulfilledList = [] // 任務隊列
        this.rejectedList = []
        handleFn(this.trigger.bind(this, 'fulfilled'), this.trigger.bind(this, 'rejected'))
    }
    trigger(status, val) {
        setTimeout(() => { // 抛進異步隊列,保證不會立即執行
            if (this.status !== 'pending') return; // 狀态一旦确定再無法變更
            this.status = status // 狀态變更
            this[`${status}List`].forEach(fn fn(val)) // 開始執行函數
        }, 0)
    }
    then(onFulfilled, onRejected) {
        return new SimplePromise((onNextFulfilled, onNextRejected) => {
            function createFinalFn(prev, next) {
                return function (params) {
                    if (typeof prev !== 'function') {
                        next(params)
                    } else {
                        const res = prev(params)
                        res && res instanceof SimplePromise ? res.then(next) : next(res)
                    }
                }
            }
            if (this.status === 'pending') {
                this.fulfilledList.push(createFinalFn(onFulfilled, onNextFulfilled))
                this.rejectedList.push(createFinalFn(onRejected, onNextRejected))
            }
        })
    }
    catch(onRejected) { // 傳回reject
        return this.then(null, onRejected)
    }
    static resolve(val) { // 直接成功執行的結果
        return new SimplePromise(resolve resolve(val))
    }
    static reject(val) { // 暴露出失敗結果,或許可以用來做Promise的中斷
        return new SimplePromise((resolve, reject) => reject(val))
    }
}      

至此大概40行的代碼實作了一個簡單的Promise,寫個例子測試下

const p = new SimplePromise((resolve, reject) => {
    resolve('success');
}).then(res {
    console.log('ok', res)
}, err {
    console.log('no', err)
})
// ok success      

注:本例中實作的簡單Promise無法跑通全部官方測試用例,隻是對實作規範中的核心部分進行一次編寫練習。

主要了解以上兩個關鍵點其實就了解了Promise的核心部分,面試中可應對大部分Promise原理考察,如果面試官要你用手寫完整的Promise實作,那你應該反問他貴公司是不是從來不使用電腦辦公。

最後用上我們手寫的SimplePromise來運作一道題加深對Promise的認識

var promise = new SimplePromise(function(resolve, reject){
  setTimeout(function() {
    resolve(1);
  }, 1000)
})

promise.then(() => {
  return SimplePromise.resolve(2);
}).then((n) => {
  console.log('我是第一個:結果是',n)
});

promise.then(() => {
  return 2
}).then((n) => {
  console.log('我是第二個:結果是',n)
});

promise.then(2).then((n) => {
  console.log('我是第三個:結果是',n)
});      
我是第二個: 結果是2
我是第三個: 結果是1
我是第一個: 結果是2      

繼續閱讀