JS Promise的用法, 以及本身模擬一個Promise

注: 本文中寫的類只是爲了瞭解Promise類的內部原理而模擬出來一個, 並不必定符合相似的規範或者效率多麼高, 可是基本的功能仍是實現了的.
注: 本文代碼運行環境: NodeJS v14.9.0數組

用法

以下, 這是一個傳統的使用回調函數的異步代碼promise

function getAnInt(callback) {
    setTimeout(() => {
        callback(81)
    }, 500)
}

function sqrt(n, resolve, reject) {
    setTimeout(() => {
        let res = Math.sqrt(n)
        if (parseInt(res) === res) {
            resolve(res)
        } else {
            reject("cannot get an int")
        }
    }, 500)
}

let errHandler = err => console.log("Error " + err)

getAnInt(v1 => {
    console.log(v1)
    sqrt(v1, v2 => {
        console.log(v2)
        sqrt(v2, v3 => {
            console.log(v3)
            sqrt(v3, v4 => {
                console.log(v4)
            }, errHandler)
        }, errHandler)
    }, errHandler)
})

執行結果:框架

81
9
3
Error cannot get an int

有沒有感受眼花繚亂? 這金字塔狀的代碼被親切地稱爲回調地獄, 下面就是咱們的主角Promise上場的時候了, 醬醬醬醬異步

function getAnInt() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(81)
        }, 500)
    })
}

function sqrt(n) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            let res = Math.sqrt(n)
            if (parseInt(res) === res) {
                resolve(res)
            } else {
                reject("cannot get an int")
            }
        }, 500)
    })
}


getAnInt().then(v1 => {
    console.log(v1)
    return sqrt(v1)
}).then(v2 => {
    console.log(v2)
    return sqrt(v2)
}).then(v3 => {
    console.log(v3)
    return sqrt(v3)
}).then(v4 => {
    console.log(v4)
}).catch(err => {
    console.log("Error " + err)
})

執行結果:函數

81
9
3
Error cannot get an int

結果如出一轍, 可是這個代碼寫出來的感受, 就是要清晰了好多好多好多好多好多好多好多好多好多好多好多好多測試

介紹

Promise/A+標準中定義了Promise究竟是個什麼東西, 這裏挑出重點部分, 其他的規範若是想看的話點這裏去官網this

  • promise 含有then方法, 沒有規定其它的方法.
  • then方法會返回一個新的promise
  • then方法的參數是onFulfilled, onRejected, 它們都是可選的(固然都是函數類型)
  • promise有三個狀態, pending(代辦), fulfilled(完成)rejected(被拒絕), 狀態只能從pending轉成另外兩個, 而後就不能再轉了.
  • 若是onRejected或者onFulfilled返回了一個Promise對象, 須要得出它的結果再傳給下一個then方法裏對應的地方

由於本文代碼中有不少的 resolve, 因此這裏的代碼使用resolved(被解決)代替fulfilledcode

爲何沒有列出來更多的內容呢, 由於其它的內容大多和兼容性有關, 與這個實現原理關係不是太大, 還有的是到具體實現函數的時候纔會用到的規範, 因此我沒有列出來對象

注: catch方法是ES6標準裏的, 它的原理是then(null, onRejected)get

實現

注: 本文代碼不考慮throw, 爲了只體現原理, 讓代碼儘量更簡單.

構造函數

Promise的構造函數一般傳入一個執行者函數, 這個函數裏面多是異步邏輯(這麼說的意思就是也可能不是), 接受兩個參數: resolvereject.

  • 調用resolve(value)就表明方法成功執行了, Promise會把resolve中傳入的value傳給then方法裏的參數

  • 調用reject(reason)就是執行出錯了, Promise會把reject中傳入的reason傳給then方法裏的參數

好, 下面開始作點準備工做

const Pending  = 'pending'
const Resolved = 'resolved'
const Rejected = 'rejected'

class MyPromise {}

誒, 這段代碼我感受不用解釋了吧? 下面的我會在註釋或者是代碼塊下方說明

class MyPromise {
    constructor(executor) {
        // 狀態
        this.status = Pending
        // 正常運行返回的結果
        this.value = null
        // 發生錯誤的緣由
        this.reason = null
        // 詳見這段代碼塊下面寫的 注1
        this.onRejected = () => {}
        this.onResolved = () => {}
        
        let resolve = value => {
            // 若是不是Pending就忽略
            if (this.status !== Pending) {
                return
            }
            this.status = Resolved
            this.value = value
            this.onResolved(value)
        }

        let reject = reason => {
            // 若是不是Pending就忽略
            if (this.status !== Pending) {
                return
            }
            this.status = Rejected
            this.reason = reason
            this.onRejected(reason)
        }
        // 見 注2
        executor(resolve, reject)
    }
}
  • 注1: 這是兩個被reject或者resolve後調用的回調函數, 我看的別人實現的版本大可能是一個數組, 而後調用的時候一個接一個調用裏面的函數.

    我認爲對同一個promise調用屢次then方法的時候不多, 並且本文只是一個思路展現, 並不嚴格遵照A+規範, 因此這裏就直接寫了個什麼也沒幹的函數

    在這裏也分析一下, 在then方法調用的時候, 若是調用then時的狀態是Pending, 那麼就設置一下當前對象裏的onRejectedonResolved, 具體設置什麼在後面的代碼裏會提到; 若是狀態不是Pending, 就表明這兩個函數早就執行完了, 就須要根據this.valuethis.reason具體的調用then函數中傳進來的onRejectedonResolved.

  • 注2: 這裏直接同步調用了, 沒有異步調用. 由於若是這個操做真的須要異步的話, 在executor函數裏面就會有異步方法了(如setTimeout), 不須要Promise類給它辦.

Then方法

而後就是then方法啦~

注意: then方法要求每次返回新的Promise對象.

先寫個框架

then(onResolved, onRejected) {
  let funcOrNull = f => typeof f === "function" ? f : null
  onResolved = funcOrNull(onResolved)
  onRejected = funcOrNull(onRejected)

  if (this.status === Rejected) {
    return new MyPromise((resolve, reject) => {

    })
  } else if (this.status === Resolved) {
    return new MyPromise((resolve, reject) => {

    })
  } else {
    return new MyPromise((resolve, reject) => {

    })
  }
}

Rejected

若是是狀態是rejected, 那麼

if (this.status === Rejected) {
    return new MyPromise((resolve, reject) => {
        let value = (onRejected === null ? reject : onRejected)(this.reason)
        if (value instanceof MyPromise) {
            value.then(resolve, reject)
        } else {
            resolve(value)
        }
    })
}

這些實現的代碼包括下面的elseif和else塊就是最難理解的了, 我當時是很久很久也沒有理解, 接下來我會就像數學裏面同樣分類討論:

關於Rejected塊的詳細說明(儘管也就10行)

理解了Rejected塊, 那麼Resolved塊和他幾乎如出一轍, 只是函數名字不同而已, 因此我這裏會分析的儘量詳細

  • 若是調用的時候是這樣的:

    new MyPromise((resolve, reject) => {
        reject("I rejected the promise")
    }).then(null, console.log)

    先分析構造方法, 建立Promise對象的時候, 這裏它的狀態就變成Rejected, 可是其餘的什麼事都沒幹, 讓咱們來看前面的代碼

    this.onRejected = () => {}
    this.onResolved = () => {}
    
    let reject = reason => {
        if (this.status !== Pending) {
            return
        }
        this.status = Rejected
        this.reason = reason
        this.onRejected(reason)
    }
    executor(resolve, reject)

    這個時候this.onRejected仍是個空函數, 因此調用它也沒什麼用
    接下來到then方法了, 讓咱們來看上面if塊裏的代碼

    return new MyPromise((resolve, reject) => {
        let value = (onRejected === null ? reject : onRejected)(this.reason)
        if (value instanceof MyPromise) {
            value.then(resolve, reject)
        } else {
            resolve(value)
        }
    })

    能夠看出它執行了let value = onRejected(reason), 而後調用resolve(value), 以後這個新的Promise狀態就是Resolved了.

    至於爲何這裏要用resolve, 我是經過NodeJS作了個實驗看看NodeJS對這件事是怎麼幹的, 代碼以下

    let p1 = new Promise((resolve, reject) => {
        reject("I rejected the promise")
    })
    
    let p2 = p1.then(null, reason => {
        return 'I am from onRejected function'
    })
    
    // 這裏是爲了避免管究竟是什麼狀態都能把p1和p2輸出出來
    p2.then(() => console.log(p1, p2), () => console.log(p1, p2))

    輸出(本來的執行結果沒有換行, 我爲了方便看本身加上的)

    Promise { <rejected> 'I rejected the promise' }
    Promise { 'I am from onRejected function' }

    這就看出來NodeJS是在處理完錯誤以後把onRejected的返回值用resolve函數處理了


  • 若是調用的時候是這樣的

    new MyPromise((resolve, reject) => {
        reject("I just rejected the promise")
    }).then(null, null).then(null, console.log)

    這個時候就要考慮不能把錯誤信息丟掉了, 爲了實現這個"穿透"功能, 咱們能夠研究一下NodeJS是怎麼幹的

    let p1 = new Promise((resolve, reject) => {
        reject("I rejected the promise")
    }).then(null, null)
    
    p1.then(() => console.log(p1), () => console.log(p1))

    輸出

    Promise { <rejected> 'I rejected the promise' }

    這就很簡單了, NodeJS是把新的Promise對象繼續調用reject而且傳遞錯誤信息. 因此再看上面if塊裏的代碼

    return new MyPromise((resolve, reject) => {
        let value = (onRejected === null ? reject : onRejected)(this.reason)
        if (value instanceof MyPromise) {
            value.then(resolve, reject)
        } else {
            resolve(value)
        }
    })

    能夠看出這裏也是在onRejected空的時候直接用reject方法把新的Promise對象的狀態設置成了Rejected而且也把this.reason錯誤信息傳了過去.
    展示成代碼的話, 就是執行了reject(this.reason)

    你可能會疑惑, 那麼reject(this.reason)返回值應該是undefined, 而後又調用了resolve(value)是怎麼回事呢?
    這裏咱們要看前面的代碼

    let resolve = value => {
        // 若是不是Pending就忽略
        if (this.status !== Pending) {
            return
        }
        this.status = Resolved
        this.value = value
        this.onResolved(value)
    }

    在調用完reject以後, 這裏的status就變成了Rejected, 這個方法就不會調用了呀


    你可能還會疑惑, 這裏的代碼

    if (value instanceof MyPromise) {
        value.then(resolve, reject)
    } else {
        resolve(value)
    }

    雖說你知道返回值是Promise要得出結果, 但這是onRejected返回的值, 爲何第二行要這麼寫?

    仍是老方法, 咱們看看NodeJS這個地方怎麼實現的

    let p = new Promise((resolve, reject) => {
        reject("I rejected the promise")
    }).then(null, reason => {
        return new Promise((resolve, reject) => {
            resolve("Hello~")
        })
    }).then(value => {
        console.log("Value " + value)
    }, reason => {
        console.log("Reason " + reason)
    })

    運行結果

    Value Hello~

    因此說, 這裏須要這麼寫, 讓這個then裏返回的Promise對象then方法的onResolved方法直接調用新對象的resolvereject方法來操做這個新對象

Resolved

若是上面的都能理解了, 那麼下面這個elseif塊就特別好理解了

else if (this.status === Resolved) {
    return new MyPromise((resolve, reject) => {
        let value = (onResolved === null ? resolve : onResolved)(this.value)
        if (value instanceof MyPromise) {
            value.then(resolve, reject)
        } else {
            resolve(value)
        }
    }
}

Pending

else塊裏, 也就是狀態是Pending的時候, 須要作的事情幾乎和上面的ifelseif塊同樣

Promise對象狀態是Pending的時候, 不能經過this.valuethis.reason獲取值, 可是, 咱們能夠經過設置this.onRejectedthis.onResolved這兩個函數, 由於當Promiseexecutor執行完的時候必定會調用這兩個函數中的一個, 而且調用它們的時候都會帶上valuereason, 因此這裏的代碼須要這麼寫

else {
    return new MyPromise((resolve, reject) => {
        this.onResolved = value => {
            let v = (onResolved === null ? resolve : onResolved)(value)
            if (v instanceof MyPromise) {
                v.then(resolve, reject)
            } else {
                resolve(v)
            }
        }

        this.onRejected = reason => {
            let v = (onRejected === null ? reject : onRejected)(reason)
            if (v instanceof MyPromise) {
                v.then(resolve, reject)
            } else {
                resolve(v)
            }
        }
    })
}

最後加上一個catch方法, 其實就是一個語法糖, 既然ES6都加上了, 那我也加上吧

catch(onRejected) {
    return this.then(null, onRejected)
}

最後的測試

嘿咻, 終於弄完了, 接下來就是實驗新對象的時候啦!(這麼說好像有點怪怪的呢)

仍是文章開頭那熟悉的味道

function getAnInt() {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve(81)
        }, 500)
    })
}

function sqrt(n) {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            let res = Math.sqrt(n)
            if (parseInt(res) === res) {
                resolve(res)
            } else {
                reject("cannot get an int")
            }
        }, 500)
    })
}


getAnInt().then(v1 => {
    console.log(v1)
    return sqrt(v1)
}).then(v2 => {
    console.log(v2)
    return sqrt(v2)
}).then(v3 => {
    console.log(v3)
    return sqrt(v3)
}).then(v4 => {
    console.log(v4)
}).catch(err => {
    console.log("Error " + err)
})

結果

81
9
3
Error cannot get an int

附: 全代碼

const Pending  = 'pending'
const Resolved = 'resolved'
const Rejected = 'rejected'

class MyPromise {
    constructor(executor) {
        // 狀態
        this.status = Pending
        // 正常運行返回的結果
        this.value = null
        // 發生錯誤的緣由
        this.reason = null
        // 見 注1
        this.onRejected = () => {}
        this.onResolved = () => {}
        
        let resolve = value => {
            // 若是不是Pending就忽略
            if (this.status !== Pending) {
                return
            }
            this.status = Resolved
            this.value = value
            this.onResolved(value)
        }

        let reject = reason => {
            // 若是不是Pending就忽略
            if (this.status !== Pending) {
                return
            }
            this.status = Rejected
            this.reason = reason
            this.onRejected(reason)
        }
        // 見 注2
        executor(resolve, reject)
    }

    then(onResolved, onRejected) {
        let funcOrNull = f => typeof f === "function" ? f : null
        onResolved = funcOrNull(onResolved)
        onRejected = funcOrNull(onRejected)

        if (this.status === Rejected) {
            return new MyPromise((resolve, reject) => {
                let value = (onRejected === null ? reject : onRejected)(this.reason)
                if (value instanceof MyPromise) {
                    value.then(resolve, reject)
                } else {
                    resolve(value)
                }
            })
        } else if (this.status === Resolved) {
            return new MyPromise((resolve, reject) => {
                let value = (onResolved === null ? resolve : onResolved)(this.value)
                if (value instanceof MyPromise) {
                    value.then(resolve, reject)
                } else {
                    resolve(value)
                }
            })
        } else {
            return new MyPromise((resolve, reject) => {
                this.onResolved = value => {
                    let v = (onResolved === null ? resolve : onResolved)(value)
                    if (v instanceof MyPromise) {
                        v.then(resolve, reject)
                    } else {
                        resolve(v)
                    }
                }

                this.onRejected = reason => {
                    let v = (onRejected === null ? reject : onRejected)(reason)
                    if (v instanceof MyPromise) {
                        v.then(resolve, reject)
                    } else {
                        resolve(v)
                    }
                }
            })
        }
    }

    catch(onRejected) {
        return this.then(null, onRejected)
    }
}

// 測試模塊!
function getAnInt() {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            resolve(81)
        }, 500)
    })
}

function sqrt(n) {
    return new MyPromise((resolve, reject) => {
        setTimeout(() => {
            let res = Math.sqrt(n)
            if (parseInt(res) === res) {
                resolve(res)
            } else {
                reject("cannot get an int")
            }
        }, 500)
    })
}


getAnInt().then(v1 => {
    console.log(v1)
    return sqrt(v1)
}).then(v2 => {
    console.log(v2)
    return sqrt(v2)
}).then(v3 => {
    console.log(v3)
    return sqrt(v3)
}).then(v4 => {
    console.log(v4)
}).catch(err => {
    console.log("Error " + err)
})

參考: https://zhuanlan.zhihu.com/p/21834559
https://zhuanlan.zhihu.com/p/183801144

相關文章
相關標籤/搜索