一步一步實現手寫Promise

Muji980701發表於2020-12-04

一步一步手寫實現Promise

Promise雛形

首先我們根據原生Promise的使用方法來分析

new Promise ((reslove, reject) => {
    resolve('resolve')
    reject('reject')
})

Promise 建構函式只有一個引數,是一個函式,我們將它命名為executor( )。這個函式在構造之後會直接被執行,所以我們稱之為起始函式。起始函式包含兩個引數 resolve 和 reject。Promise 物件有以下兩個特點:
1、物件的狀態不受外界影響。Promise 物件代表一個非同步操作,有三種狀態:

  • pending: 初始狀態,不是成功或失敗狀態。
  • fulfilled: 意味著操作成功完成。
  • rejected: 意味著操作失敗。

只有非同步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。這也是 Promise 這個名字的由來,它的英語意思就是「承諾」,表示其他手段無法改變。

2、一旦狀態改變,就不會再變,任何時候都可以得到這個結果。Promise 物件的狀態改變,只有兩種可能:從 Pending 變為 Resolved 和從 Pending 變為 Rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果(狀態保護)。
那麼我們可以可以根據這三種不同狀態去實現resolve、reject,以及實現then方法,那麼一個簡單的promise雛形就出來了。下面來實現它:

class Promi{
    static PENDING = 'pending'
    static FULFILLED = 'fulfilled'
    static REJECTED = 'rejected'
    constructor(executor) {
        this.status = Promi.PENDING
        this.value = null
        //對executor()進行異常捕獲,交給reject()處理
        try {
            executor(this.resolve.bind(this), this.reject.bind(this))
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(value) {
    //if是為了進行狀態保護
        if(this.status == Promi.PENDING){
            this.status = Promi.FULFILLED
            this.value = value
        }
        
    }
    reject(reason) {
        if(this.status == Promi.PENDING){
            this.status = Promi.REJECTED
            this.value = reason        
        }
    }
}

then( )的基礎構建

對於已經例項化過的 promise 物件可以呼叫 promise.then() 方法,還是先觀察原生Promise裡的用法

promise.then(onFulfilled, onRejected)

於是我們對then( )進行基礎構建,

    then(onFulfilled, onRejected) {
        if(this.status == Promi.FULFILLED) {
            onFulfilled(this.value)
        }
        if(this.status == Promi.REJECTED) {
            onRejected(this.value)
        }      
    }

可是我們在使用promise物件時常常碰到這種情況

let p = new Promi((resolve, reject) => {
        reject("reject")
    })
    p.then(value => {
        console.log('成功', value)
    })

即onFulfilled和onRejected兩個引數是可以不傳的,如果我們直接用上面的寫法,會導致報錯,因為status變為了REJECTED卻不能呼叫對應的onRejected()函式。於是我們對then( )方法進行以下改寫。

then(onFulfilled, onRejected) {
        //如果傳遞的引數為null,將其封裝成函式
        if (typeof onFulfilled != 'function') {
            onFulfilled = () => {}
        }
        if (typeof onRejected != 'function') {
            onRejected = () => {}
        }
        if(this.status == Promi.FULFILLED) {
            onFulfilled(this.value)
        }
        if(this.status == Promi.REJECTED) {
            onRejected(this.value)
        }
        
    }

then( )的非同步執行

進行到這裡,我們不能忘記Promise的初衷是為了更加優雅地書寫複雜的非同步任務。現在這樣的寫法會使then方法阻塞我們的同步任務。於是我們在then中onFulfilled和onRejected方法的執行外面包上一個計時器,這樣便不會立即執行方法,而是將其放入任務佇列中,等待同步任務執行完成後對其進行輪詢。

then(onFulfilled, onRejected) {
        //如果傳遞的引數為null,將其封裝成函式
        if (typeof onFulfilled != 'function') {
            onFulfilled = () => {}
        }
        if (typeof onRejected != 'function') {
            onRejected = () => {}
        }
        if(this.status == Promi.FULFILLED) {
            setTimeout(() => {
                try {
                    onFulfilled(this.value)
                } catch (error) {
                    onRejected(error)
                }
            })   
        }
        if(this.status == Promi.REJECTED) {
            setTimeout(() => {
                try {
                    onRejected(this.value)
                } catch (error) {
                    onRejected(error)          
                }
            })     
        }
    }
}

promise的pending狀態處理

下面我們看看這種情況

let p = new Promi((resolve, reject) => {
        setTimeout(() => {
            reject("拒絕")
        }, 1000)
    })
    p.then(value => {
        console.log(value)
    },
    reason => {
        console.log(reason)
    })
    console.log("同步任務")

如果executor( )中包含一個計時器,即this.status的改變有延時,那按照之前的寫法,在then方法中不會執行任何一個回撥函式。我們在這裡需要在Promi類中增加一個屬性:callbacks陣列,來儲存我們在then中的回撥。當promise在pending狀態時將回撥函式壓入棧中,當執行reslove()或reject()時再進行呼叫。
then()中新增

if(this.status == Promi.PENDING) {
            this.callbacks.push({
                onFulfilled,
                onRejected
            })
        }

reslove()和reject()改寫為

resolve(value) {
    //if是為了進行狀態保護
        if(this.status == Promi.PENDING){
            this.status = Promi.FULFILLED
            this.value = value
            setTimeout(() => {
                this.callbacks.map(callback => {
                    callback.onFulfilled(this.value)
            })
            })
        }
    }
    reject(reason) {
        if(this.status == Promi.PENDING){
            this.status = Promi.REJECTED
            this.value = reason  
            setTimeout(() => {
                this.callbacks.map(callback => {
                callback.onRejected(this.value)
                })     
            }) 
        }
    }

注意還是要在呼叫onFulfilled和onRejected外面包裹一層定時器以保證then的非同步執行。

promise的鏈式呼叫

promise的鏈式呼叫是其非常重要的特性。其原理是then()方法會返回一個新的Promise例項,所以then()方法後面可以繼續跟另一個then()方法進行鏈式呼叫。

   then(onFulfilled, onRejected) {
        //如果傳遞的引數為null,將其封裝成函式
        if (typeof onFulfilled != 'function') {
            onFulfilled = () => {}
        }
        if (typeof onRejected != 'function') {
            onRejected = () => {}
        }
        return new Promi((resolve,reject) => {
            if(this.status == Promi.PENDING) {
                this.callbacks.push({
                    onFulfilled: value => {
                        try {
                            let result =  onFulfilled(value)
                            resolve(result)     
                        } catch (error) {
                            reject(error)
                        }      
                    },
                    onRejected: value => {
                        try {
                            let result = onRejected(value)
                            resolve(result)
                        } catch (error) {
                            reject(error)      
                        }
                        
                    }
                })
        }
            if(this.status == Promi.FULFILLED) {
                setTimeout(() => {
                    try {
                        let result = onFulfilled(this.value)
                        resolve(result)
                    } catch (error) {
                        reject(error)
                    }
                })   
            }
            if(this.status == Promi.REJECTED) {
                setTimeout(() => {
                    try {
                        let result = onRejected(this.value)
                        resolve(result)
                    } catch (error) {
                        reject(error)          
                    }
                })     
            }
        })
    }

注意,狀態轉換時要呼叫resolve()和reject()才能將this.value傳遞給下一個then()。

相關文章