Promise 簡單實現

zdddrszj發表於2019-02-26

今天的任務是根據Promise A+ 規範簡單實現一個 Promise 庫。開始之前,我們可以先通讀一下這個規範,戳這裡

靜心讀一下這個規範真的很重要,很重要,很重要。雖然是英文版,但是讀起來也許會比漢語更能讓人理解。

A promise represents the eventual result of an asynchronous operation. The primary way of interacting with a promise is through its then method, which registers callbacks to receive either a promise’s eventual value or the reason why the promise cannot be fulfilled.

提到 Promise 自然想到非同步,非同步想到回撥,回撥就是回頭在調。回頭在調的前提是提前儲存回撥內容,等到時間後從儲存的地方取到對應的內容再執行即可。

一、Promise 建構函式實現

2.1 A promise must be in one of three states: pending, fulfilled, or rejected.

2.1.1 A pending promise may transition to either the fulfilled or rejected state.

2.1.2 A fulfilled promise must have a value, which must not change.

2.1.3 A rejected promise must have a reason, which must not change.

我們平時但凡做什麼事情,大概都有三種基本狀態,正在進行、成功、失敗。一個 Promise 代表一個非同步操作,也有這三種狀態,且狀態只能從正在進行到成功或者失敗,不可逆轉。不管事情最終什麼狀態,既然結果出來了,那就通知之前快取回撥的陣列(因為可以 then 多次,故為陣列)並把結果給它吧。

Promise 基本使用:

let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(`success`)
    }, 1000)
})
promise.then((data) => {console.log(data)})
複製程式碼

首先 Promise 是一個類,通過 new 建立一個例項,引數是一個函式,函式引數預設命名為 resolvereject,我們稱該函式為一個執行器 executor,預設執行。構造函中還需要一個狀態變數 status,儲存當前執行狀態;以及執行成功 value 或失敗 reason 的結果變數;最後還需要上面提及的快取回撥的陣列。實現程式碼如下:

class Promise () {
    constructor (executor) {
        // 預設等待態
        this.status = `pending`
        // 成功結果
        this.value = undefined
        // 失敗原因
        this.reason = undefined
        // 儲存成功回撥陣列
        this.onResolvedCallbacks = []
        // 儲存失敗回撥陣列
        this.onRejectedCallbacks = []
        // 執行器
        executor(resolve, reject)
    }
}
複製程式碼

執行器函式引數是 resolvereject, 同樣也是函式,功能是有結果後觸發快取回撥陣列,也可以理解為釋出。實現程式碼如下:

class Promise () {
    constructor (executor) {
        // 變數宣告
        this.status = `pending`
        this.value = undefined
        this.reason = undefined
        this.onResolvedCallbacks = []
        this.onRejectedCallbacks = []
        // 結果成功,通知函式定義
        let resolve = (value) => {
            if (this.status === `pending`) {
                this.status = `resolved`
                this.value = value
                this.onFulfilledCallbacks.forEach(fn => fn())
            }
        }
        // 結果失敗,通知函式定義
        let reject = (reason) => {
            if (this.status === `pending`) {
                this.status = `rejected`
                this.reason = reason
                this.onRejectedCallbacks.forEach(fn => fn())
            }
        }
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }
}
複製程式碼

既然有結果後要觸發快取回撥陣列,那麼接下來我們看一下這個回撥陣列如何快取。

二、Promise.then 實現

2.2 A promise must provide a then method to access its current or eventual value or reason.

1、then 訂閱功能

我們都知道,promise 例項只有通過呼叫 then 函式才能拿到結果,then 函式引數分別為成功回撥和失敗回撥,回撥引數為成功值和失敗值。固 then 函式的主要功能即是快取回撥函式,也可以理解為訂閱。實現程式碼如下:

class Promise () {
    // constructor省略...
    then (onFulfilled, onRejected) {
        // 如果exector內容為同步程式碼,resolve函式執行後,狀態已變為resolved,則直接執行回撥
        if (this.status === `resolved`) {
            onFulfilled(this.value)
        } 
        // 如果exector內容為同步程式碼,reject函式執行後,狀態已變為rejected,直接執行回撥
        if (this.status === `rejected`) {
           onRejected(this.reason)
        }
        // 如果exector內容為非同步程式碼,狀態為pending時,則快取回撥函式,我們也可以理解為訂閱
        if (this.status === `pending`) {
            this.onResolvedCallbacks.push(() => {
               onFulfilled(this.value)
            })
            this.onRejectedCallbacks.push(() => {
                onRejected(this.reason)
            })
        }
    }
}
複製程式碼

2、then 返回 promise

2.2.6 Then may be called multiple times on the same promise.

2.2.7 Then must return a promise. promise2 = promise1.then(onFulfilled, onRejected)

實現程式碼如下:

class Promise () {
    // constructor省略...
    then (onFulfilled, onRejected) {
        let promise2
        if (this.status === `resolved`) {
            promise2 = new Promise((resolve, reject) => {
                onFulfilled(this.value)
            })
        }
        if (this.status === `rejected`) {
            promise2 = new Promise((resolve, reject) => {
                onRejected(this.reason)
            })
        }
        if (this.status === `pending`) {
            promise2 = new Promise((resolve, reject) => {
                this.onResolvedCallbacks.push(() => {
                    onFulfilled(this.value)
                })
                this.onRejectedCallbacks.push(() => {
                    onRejected(this.reason)
                })
            })
        }
        return promise2
    }
}
複製程式碼

2、then 呼叫多次

2.2.7.1 onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).

then 可以呼叫多次,且每次 then 引數 onFulFilled 函式的引數值為上一次 promise 函式的執行結果,即為 onFulfilled(this.value) 的執行結果,我們設為 x。根據 x 的不同情況做不同處理,詳情見 Promise A+ 規範 2.3 Promise Resolution Procedure

實現程式碼如下:

class Promise () {
    // constructor省略...
    then (onFulfilled, onRejected) {
        let promise2
        if (this.status === `resolved`) {
            promise2 = new Promise((resolve, reject) => {
                let x = onFulfilled(this.value)
                // Promise Resolution Procedure
                resolvePromise(promise2, x, resolve. reject)
            })
        }
        if (this.status === `rejected`) {
            promise2 = new Promise((resolve, reject) => {
                let x = onRejected(this.reason)
                // Promise Resolution Procedure
                resolvePromise(promise2, x, resolve. reject)
            })
        }
        if (this.status === `pending`) {
            promise2 = new Promise((resolve, reject) => {
                this.onResolvedCallbacks.push(() => {
                    let x = onFulfilled(this.value)
                    // Promise Resolution Procedure
                    resolvePromise(promise2, x, resolve. reject)
                })
                this.onRejectedCallbacks.push(() => {
                    let x = onRejected(this.reason)
                    resolvePromise(promise2, x, resolve, reject)
                })
            })
        }
        return promise2
    }
}
// 處理函式:處理promise2和x的關係
function resolvePromise (promise2, x, resolve, reject) {
    // 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
    if (promise2 === x) {
        return reject(new TypeError(`迴圈引用`))
    }
    //  2.3.3 if x is an object or function
    if (x !== null && typeof x === `object` || typeof x === `function` ){
        try {
            // 2.3.3.1 Let then be x.then.
            let then = x.then
            // 2.3.3.3 If then is a function,call it with x as this.
            if (typeof then === `function`) {
                // 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
                then.call(x, y => {
                    resolvePromise(promise2, y, resolve, reject)
                }, r => {
                    // 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
                    reject(r)
                })
            }
        } catch (e) {
            // 2.3.3.4 If calling then throws an exception e
            reject(e)
        }
    } else {
        // 2.3.4 If x is not an object or function, fulfill promise with x.
        resolve(x)
    }
}
複製程式碼

3、狀態不可逆轉

實現程式碼如下:

// 處理函式:處理promise2和x的關係
function resolvePromise (promise2, x, resolve, reject) {
    // 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
    if (promise2 === x) {
        return reject(new TypeError(`迴圈引用`))
    }
    let called
    //  2.3.3 if x is an object or function
    if (x !== null && typeof x === `object` || typeof x === `function` ){
        try {
            // 2.3.3.1 Let then be x.then.
            let then = x.then
            // 2.3.3.3 If then is a function,call it with x as this.
            if (typeof then === `function`) {
                // 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
                then.call(x, y => {
                    // `2.3.3.3.3 If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.`
                    if (called) return
                    called = true
                    resolvePromise(promise2, y, resolve, reject)
                }, r => {
                    if (called) return
                    called = true
                    // 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
                    reject(r)
                })
            }
        } catch (e) {
            // `2.3.3.3.4 If calling then throws an exception e,2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.`
            if (called) return
            called = true
            // 2.3.3.4 If calling then throws an exception e
            reject(e)
        }
    } else {
        // 2.3.4 If x is not an object or function, fulfill promise with x.
        resolve(x)
    }
}
複製程式碼

4、Promise.then 非同步執行

實現程式碼如下:

class Promise () {
    then (onFulfilled, onRejected) {
        let promise2
        if (this.status === `resolved`) {
            promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            })
        }
        if (this.status === `rejected`) {
            promise2 = new Promise((resolve, reject) => {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                }. 0)
            })
        }
        if (this.status === `pending`) {
            promise2 = new Promise((resolve, reject) => {
                this.onResolvedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value)
                            promiseResolve(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    }, 0)
                })
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            promiseResolve(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    }, 0)
                })
            })
        }
        return promise2
    }
}
複製程式碼

5、Promise.then 穿透

promise.then().then() 多次,都可以獲取到最終結果,固當 then 引數 onFulfilled 為空時,需要自動 return value;當 onRejected 為空時,需要 return throw err。實現程式碼如下:

class Promise () {
    then (onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === `function` ? onFulfilled : (value) => { return value }
        onRejected = typeof onRejected === `function` ? onRejected : (err) => { throw err }
        // promise2省略...
    }
}
複製程式碼

三、其它方法實現

1、Promise.resolve

實現程式碼如下:

Promise.resolve = function (val) {
    return new Promise((resolve, reject) => {
        resolve(val)
    })
}
複製程式碼

2、Promise.reject

實現程式碼如下:

Promise.reject = function (val) {
    return new Promise((resolve, reject) => {
        reject(val)
    })
}
複製程式碼

3、Promise.all

實現程式碼如下:

Promise.all = function (promises) {
    return new Promise((resolve, reject) => {
        let dataArr = []
        let count = 0
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(data => {
                dataArr[i] = data
                count++
                if (count === promises.lenth) {
                    resolve(dataArr)
                }
            }, reject)
        }
    })
}
複製程式碼

4、Promise.race

實現程式碼如下:

Promise.race = function (promises) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i < promises.length; i++) {
            promise[i].then(resolve, reject)
        }
    })
}
複製程式碼

5、Promise.promisify

實現程式碼如下:

Promise.promisify = function (fn) {
    return function (...args) {
        return new Promise((resolve, reject) => {
            fn(...args, (err, data) => {
                if (err) reject(err)
                resolve(data)
            })
        })
    }
}
複製程式碼

四、Promise 校驗

要想驗證自己實現的 Promise 是否符合 Promise A+ 規範,可以全域性安裝 promises-aplus-tests 工具包。

Adapters

In order to test your promise library, you must expose a very minimal adapter interface. These are written as Node.js modules with a few well-known exports:

  • resolved(value): creates a promise that is resolved with value.
  • rejected(reason): creates a promise that is already rejected with reason.
  • deferred(): creates an object consisting of { promise, resolve, reject }

The resolved and rejected exports are actually optional, and will be automatically created by the test runner using deferred

resolvedrejected 是可選的,下面我們實現一下 deferred,實現程式碼如下:

Promise.deferred = function () {
    let dfd = {}
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve
        dfd.reject = reject
    })
    return dfd
}
複製程式碼

到這裡一切準備就緒,校驗步驟如下:

npm install promises-aplus-tests -g
promises-aplus-tests ./Promise.js
複製程式碼

原始碼

好了,到這裡全部程式碼都已呈現,趕快自己親手試一下吧!???

相關文章