從零手寫逐步實現Promise A+標準的所有方法

Peter譚金傑發表於2019-11-18

~

Promise

image.png

本文皆在實現Promise的所有方法,程式碼均測試可執行,編寫於2019年11月17日

GitHub倉庫更有自己實現的webpack、mini-react、redux、react-redux、websocket , Electron跨平臺桌面端、React-native移動端開源專案等

倉庫地址:

https://github.com/JinJieTan

回顧Promise:

new Promise中的構造器函式同步執行

then的回撥函式以微任務的形式執行

呼叫resolve,reject後,狀態不能再被改變,傳遞的值也是

每次.then後返回的還是一個promise

promise可以巢狀

其餘後面會談到

正式開始:

乞丐版:

image.png

編寫邏輯:

1.Promisenew 呼叫

2.每次失敗或者成功需要指定回撥函式,並且可以傳遞值

3.Promise擁有.then方法

上面程式碼有個問題,狀態改變應該是非同步的,.then應該是微任務形式執行

非同步改變狀態並且支援三種狀態版本:

編寫思路

狀態只能由Pending改變,而且只能改變一次

非同步改變狀態,非同步的執行.then

支援鏈式呼叫

寫到這裡,需要暫停,捋一捋思路。

萬事開頭難,其實編寫一個Promise是非常簡單的事情,看懂上面這兩段程式碼,然後徹底搞清楚這三點,再往下看。

Promise的構造器函式是同步執行

resolve、reject的呼叫是同步呼叫,非同步執行.例如resolve()是同步呼叫了resolve這個函式,但是resolve函式內部的程式碼是非同步的。---即非同步改變狀態,非同步執行.then

new Promise((resolve,reject)=>{ 
console.log('這裡是同步執行') 
resolve(‘resolve函式內部是非同步執行’)
}).then(()=>{
  console.log('這裡等resolve函式內部非同步執行,狀態改變以後再執行')
})

徹底搞懂上面的這個例子和兩句話,然後你就可以往下看了,其實下面也都是一些重複或者細節處理的工作

支援.then的鏈式呼叫

想支援鏈式操作,其實很簡單,首先儲存回撥時要改為使用陣列


self.onFulfilledCallbacks = [];
self.onRejectedCallbacks = []
 

當然執行回撥時,也要改成遍歷回撥陣列執行回撥函式

最後,then方法也要改一下,只需要在最後一行加一個return this即可,這其實和jQuery鏈式操作的原理一致,每次呼叫完方法都返回自身例項,後面的方法也是例項的方法,所以可以繼續執行。

編寫思路

1.將所有的成功與失敗回撥函式存在陣列中,遍歷執行

2.為了支援鏈式呼叫,返回this例項物件即可

3.每次改變狀態,清空所有的佇列回撥

4.目前這種方式只支援同步的回撥,下面會加入支援非同步


非同步鏈式呼叫,好像是這裡最難的點,但是在應用層裡的難點,加一箇中間層就能解決,實在不行加兩個 ---來自國內不知名碼農

看下面這段Node.js程式碼:

上面場景,我們讀取完1.txt後並列印1.txt內容,再去讀取2.txt並列印2.txt內容,再去讀取3.txt並列印3.txt內容,而讀取檔案都是非同步操作,所以都是返回一個promise

我們上一節實現的promise可以實現執行完非同步操作後執行後續回撥,但是本節的回撥讀取檔案內容操作並不是同步的,而是非同步的,所以當讀取完1.txt後,執行它回撥onFulfilledCallbacks裡面的f1,f2,f3時,非同步操作還沒有完成,所以我們本想得到這樣的輸出:

this is 1.txt
this is 2.txt
this is 3.txt

但是實際上卻會輸出

this is 1.txt
this is 1.txt
this is 1.txt

上面遇到的問題,有點像一個面試題,考閉包的一個迴圈。看列印輸出幾,大家應該有印象。


支援非同步鏈式呼叫

每次.then返回一個新的promise,這個新的promise擁有它獨自對應的成功和失敗回撥(相當於中間層)

同樣每次狀態改變就清空當前promise對應的回撥佇列

修改.then方法

.then在鏈式呼叫中會被執行多次,這裡是本文的重點

思路解析:

首先判斷當前Promise的狀態,如果狀態沒有改變,那就全部新增到佇列中

呼叫resolve函式,同樣清空佇列中所有的任務,不同點在於bridgePromise這個用來橋接的Promise(看成中間層),下面給出例子詳解

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    const self = this;
    let bridgePromise;
    //防止使用者不傳成功或失敗回撥函式,所以成功失敗回撥都給了預設回撥函式
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected = typeof onRejected === "function" ? onRejected : error => { throw error };
    if (self.status === FULFILLED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        })
    }
    if (self.status === REJECTED) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(self.error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
    if (self.status === PENDING) {
        return bridgePromise = new MyPromise((resolve, reject) => {
            self.onFulfilledCallbacks.push((value) => {
                try {
                    let x = onFulfilled(value);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
            self.onRejectedCallbacks.push((error) => {
                try {
                    let x = onRejected(error);
                    resolvePromise(bridgePromise, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
    }
}

這個例子,裡面有非同步的程式碼,但是Promise可以做到有序執行

我們看bridgePromise的原始碼:

程式碼很簡單 就是遞迴呼叫,直到返回的不是一個Promise,那麼呼叫resolve清空佇列,並且把返回的值儲存在self屬性上,提供給下一個任務使用。

下面就是流程圖:

這裡一定要看清楚,本文的重點基本都在這裡。


符合Promise A+規範,修改resolvePromise函式即可


function resolvePromise(bridgepromise, x, resolve, reject) {
    //2.3.1規範,避免迴圈引用
    if (bridgepromise === x) {
        return reject(new TypeError('Circular reference'));
    }
    let called = false;
    //這個判斷分支其實已經可以刪除,用下面那個分支代替,因為promise也是一個thenable物件
    if (x instanceof MyPromise) {
        if (x.status === PENDING) {
            x.then(y => {
                resolvePromise(bridgepromise, y, resolve, reject);
            }, error => {
                reject(error);
            });
        } else {
            x.then(resolve, reject);
        }
        // 2.3.3規範,如果 x 為物件或者函式
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try {
            // 是否是thenable物件(具有then方法的物件/函式)
            //2.3.3.1 將 then 賦為 x.then
            let then = x.then;
            if (typeof then === 'function') {
            //2.3.3.3 如果 then 是一個函式,以x為this呼叫then函式,且第一個引數是resolvePromise,第二個引數是rejectPromise
                then.call(x, y => {
                    if (called) return;
                    called = true;
                    resolvePromise(bridgepromise, y, resolve, reject);
                }, error => {
                    if (called) return;
                    called = true;
                    reject(error);
                })
            } else {
            //2.3.3.4 如果 then不是一個函式,則 以x為值fulfill promise。
                resolve(x);
            }
        } catch (e) {
        //2.3.3.2 如果在取x.then值時丟擲了異常,則以這個異常做為原因將promise拒絕。
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

實現es6 promise的all,race,resolve,reject方法

all方法:

race 方法

resolve,reject方法~ 快速定義一個成功或者失敗狀態的Promise

實現promisify方法~

實現catch方法~其實就是不傳第一個引數.then方法的語法糖

到這裡,一個Promise 並且符合A+規範的所有方法就實現了,網上的實現方式有很多都不一樣,本文以一個比較簡單明瞭的方式去實現了它。希望能幫助到大家更清楚的瞭解Promise

後面會針對下面的內容出專題系列文章~ 歡迎關注本公眾號:前端巔峰

1.從零手寫一個React

2.從零編寫一個webpack

3.從零實現一個websocket

4.從零實現一個vue

5.優雅的讓react和vue一起開發

本公眾號注重關注技術方向

即時通訊

Electron跨平臺桌面端、React-native移動端、Taro小程式

Node.js全棧工程師方向、分散式微服務

原生JavaScript

相關文章