一起學習造輪子(一):從零開始寫一個符合Promises/A+規范的promise


本文是一起學習造輪子系列的第一篇,本篇我們將從零開始寫一個符合Promises/A+規范的promise,本系列文章將會選取一些前端比較經典的輪子進行源碼分析,並且從零開始逐步實現,本系列將會學習Promises/A+,Redux,react-redux,vue,dom-diff,webpack,babel,kao,express,async/await,jquery,Lodash,requirejs,lib-flexible等前端經典輪子的實現方式,每一章源碼都托管在github上,歡迎關注~

相關系列文章:

一起學習造輪子(一):從零開始寫一個符合Promises/A+規范的promise

一起學習造輪子(二):從零開始寫一個Redux

一起學習造輪子(三):從零開始寫一個React-Redux

本系列github倉庫:

一起學習造輪子系列github(歡迎star~)

前言

Promise 是異步編程的一種解決方案,比傳統的解決方案回調函數和事件更合理更強大。它由社區最早提出和實現,ES6 將其寫進了語言標准,統一了用法,原生提供了Promise對象。本篇不注重講解promise的用法,關於用法,可以看阮一峰老師的ECMAScript 6系列里面的Promise部分:

ECMAScript 6 : Promise對象

本篇主要講解如何從零開始一步步的實現promise各項特性及功能,最終使其符合Promises/A+規范,因為講解較細,所以文章略長。
另外,每一步的項目源碼都在github上,可以對照參考,每一步都有對應的項目代碼及測試代碼,喜歡的話,歡迎給個star~

項目地址:本文代碼的github倉庫

代碼倉庫

開始

本文promise里用到的異步操作的示例都是使用的node里面的fs.readFile方法,在瀏覽器端可以使用setTimeout方法進行模擬異步操作。

一. 基礎版本

目標

  1. 可以創建promise對象實例。
  2. promise實例傳入的異步方法執行成功就執行注冊的成功回調函數,失敗就執行注冊的失敗回調函數。

實現

function MyPromise(fn) {
    let self = this; // 緩存當前promise實例
    self.value = null; //成功時的值
    self.error = null; //失敗時的原因
    self.onFulfilled = null; //成功的回調函數
    self.onRejected = null; //失敗的回調函數

    function resolve(value) {
        self.value = value;
        self.onFulfilled(self.value);//resolve時執行成功回調
    }

    function reject(error) {
        self.error = error;
        self.onRejected(self.error)//reject時執行失敗回調
    }
    fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    //在這里給promise實例注冊成功和失敗回調
    this.onFulfilled = onFulfilled;
    this.onRejected = onRejected;
}
module.exports = MyPromise

代碼很短,邏輯也非常清晰,在then中注冊了這個promise實例的成功回調和失敗回調,當promise reslove時,就把異步執行結果賦值給promise實例的value,並把這個值傳入成功回調中執行,失敗就把異步執行失敗原因賦值給promise實例的error,並把這個值傳入失敗回調並執行。

本節代碼

基礎版本代碼

二. 支持同步任務

我們知道,我們在使用es6 的promise時,可以傳入一個異步任務,也可以傳入一個同步任務,但是我們的上面基礎版代碼並不支持同步任務,如果我們這樣寫就會報錯:

let promise = new Promise((resolve, reject) => {
    resolve("同步任務執行")
});

為什么呢?因為是同步任務,所以當我們的promise實例reslove時,它的then方法還沒執行到,所以回調函數還沒注冊上,這時reslove中調用成功回調肯定會報錯的。

目標

使promise支持同步方法

實現

function resolve(value) {
    //利用setTimeout特性將具體執行放到then之后
    setTimeout(() => {
        self.value = value;
        self.onFulfilled(self.value)
    })
}

function reject(error) {
    setTimeout(() => {
        self.error = error;
        self.onRejected(self.error)
    })
}

實現很簡單,就是在reslove和reject里面用setTimeout進行包裹,使其到then方法執行之后再去執行,這樣我們就讓promise支持傳入同步方法,另外,關於這一點,Promise/A+規范里也明確要求了這一點。

2.2.4 onFulfilled or onRejected must not be called until the execution context stack contains only platform code.

本節代碼

支持同步任務代碼

三. 支持三種狀態

我們知道在使用promise時,promise有三種狀態:pending(進行中)、fulfilled(已成功)和rejected(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態。另外,promise一旦狀態改變,就不會再變,任何時候都可以得到這個結果promise對象的狀態改變,只有兩種可能:從pending變為fulfilled和從pending變為rejected。只要這兩種情況發生,狀態就凝固了,不會再變了,會一直保持這個結果,如果改變已經發生了,你再對promise對象添加回調函數,也會立即得到這個結果。

目標

  1. 實現promise的三種狀態。
  2. 實現promise對象的狀態改變,改變只有兩種可能:從pending變為fulfilled和從pending變為rejected。
  3. 實現一旦promise狀態改變,再對promise對象添加回調函數,也會立即得到這個結果。

實現

//定義三種狀態
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function MyPromise(fn) {
    let self = this;
    self.value = null;
    self.error = null;
    self.status = PENDING;
    self.onFulfilled = null;
    self.onRejected = null;

    function resolve(value) {
        //如果狀態是pending才去修改狀態為fulfilled並執行成功邏輯
        if (self.status === PENDING) {
            setTimeout(function() {
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilled(self.value);
            })
        }
    }

    function reject(error) {
        //如果狀態是pending才去修改狀態為rejected並執行失敗邏輯
        if (self.status === PENDING) {
            setTimeout(function() {
                self.status = REJECTED;
                self.error = error;
                self.onRejected(self.error);
            })
        }
    }
    fn(resolve, reject);
}
MyPromise.prototype.then = function(onFulfilled, onRejected) {
    if (this.status === PENDING) {
        this.onFulfilled = onFulfilled;
        this.onRejected = onRejected;
    } else if (this.status === FULFILLED) {
        //如果狀態是fulfilled,直接執行成功回調,並將成功值傳入
        onFulfilled(this.value)
    } else {
        //如果狀態是rejected,直接執行失敗回調,並將失敗原因傳入
        onRejected(this.error)
    }
    return this;
}
module.exports = MyPromise

首先,我們建立了三種狀態"pending","fulfilled","rejected",然后我們在reslove和reject中做判斷,只有狀態是pending時,才去改變promise的狀態,並執行相應操作,另外,我們在then中判斷,如果這個promise已經變為"fulfilled"或"rejected"就立刻執行它的回調,並把結果傳入。

本節代碼

支持三種狀態代碼

四. 支持鏈式操作

我們平時寫promise一般都是對應的一組流程化的操作,如這樣:

promise.then(f1).then(f2).then(f3)

但是我們之前的版本最多只能注冊一個回調,這一節我們就來實現鏈式操作。

目標

使promise支持鏈式操作

實現

想支持鏈式操作,其實很簡單,首先存儲回調時要改為使用數組

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

當然執行回調時,也要改成遍歷回調數組執行回調函數


self.onFulfilledCallbacks.forEach((callback) => callback(self.value));

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

MyPromise.prototype.then = function(onFulfilled, onRejected) {
    if (this.status === PENDING) {
        this.onFulfilledCallbacks.push(onFulfilled);
        this.onRejectedCallbacks.push(onRejected);
    } else if (this.status === FULFILLED) {
        onFulfilled(this.value)
    } else {
        onRejected(this.error)
    }
    return this;
}

本節代碼

支持鏈式操作代碼

五. 支持串行異步任務

我們上一節實現了鏈式調用,但是目前then方法里只能傳入同步任務,但是我們平常用promise,then方法里一般是異步任務,因為我們用promise主要用來解決一組流程化的異步操作,如下面這樣的調取接口獲取用戶id后,再根據用戶id調取接口獲取用戶余額,獲取用戶id和獲取用戶余額都需要調用接口,所以都是異步任務,如何使promise支持串行異步操作呢?

getUserId()
    .then(getUserBalanceById)
    .then(function (balance) {
        // do sth 
    }, function (error) {
        console.log(error);
    });

目標

使promise支持串行異步操作

實現

這里為方便講解我們引入一個常見場景:用promise順序讀取文件內容,場景代碼如下:

let p = new Promise((resolve, reject) => {
    fs.readFile('../file/1.txt', "utf8", function(err, data) {
        err ? reject(err) : resolve(data)
    });
});
let f1 = function(data) {
    console.log(data)
    return new Promise((resolve, reject) => {
        fs.readFile('../file/2.txt', "utf8", function(err, data) {
            err ? reject(err) : resolve(data)
        });
    });
}
let f2 = function(data) {
    console.log(data)
    return new Promise((resolve, reject) => {
        fs.readFile('../file/3.txt', "utf8", function(err, data) {
            err ? reject(err) : resolve(data)
        });
    });
}
let f3 = function(data) {
    console.log(data);
}
let errorLog = function(error) {
    console.log(error)
}
p.then(f1).then(f2).then(f3).catch(errorLog)

//會依次輸出
//this is 1.txt
//this is 2.txt
//this is 3.txt

上面場景,我們讀取完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

所以要想實現異步操作串行,我們不能將回調函數都注冊在初始promise的onFulfilledCallbacks里面,而要將每個回調函數注冊在對應的異步操作promise的onFulfilledCallbacks里面,用讀取文件的場景來舉例,f1要在p的onFulfilledCallbacks里面,而f2應該在f1里面return的那個Promise的onFulfilledCallbacks里面,因為只有這樣才能實現讀取完2.txt后才去打印2.txt的結果。

但是,我們平常寫promise一般都是這樣寫的: promise.then(f1).then(f2).then(f3),一開始所有流程我們就指定好了,而不是在f1里面才去注冊f1的回調,f2里面才去注冊f2的回調。

如何既能保持這種鏈式寫法的同時又能使異步操作銜接執行呢?我們其實讓then方法最后不再返回自身實例,而是返回一個新的promise即可,我們可以叫它bridgePromise,它最大的作用就是銜接后續操作,我們看下具體實現代碼:

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);
                }
            });
        });
    }
}
//catch方法其實是個語法糖,就是只傳onRejected不傳onFulfilled的then方法
MyPromise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}
//用來解析回調函數的返回值x,x可能是普通值也可能是個promise對象
function resolvePromise(bridgePromise, x, resolve, reject) {
   //如果x是一個promise
    if (x instanceof MyPromise) {
        //如果這個promise是pending狀態,就在它的then方法里繼續執行resolvePromise解析它的結果,直到返回值不是一個pending狀態的promise為止
        if (x.status === PENDING) {
            x.then(y => {
                resolvePromise(bridgePromise, y, resolve, reject);
            }, error => {
                reject(error);
            });
        } else {
            x.then(resolve, reject);
        }
        //如果x是一個普通值,就讓bridgePromise的狀態fulfilled,並把這個值傳遞下去
    } else {
        resolve(x);
    }
}

首先,為防止使用者不傳成功回調函數或不失敗回調函數,我們給了默認回調函數,然后無論當前promise是什么狀態,我們都返回一個bridgePromise用來銜接后續操作。

另外執行回調函數時,因為回調函數既可能會返回一個異步的promise也可能會返回一個同步結果,所以我們把直接把回調函數的結果托管給bridgePromise,使用resolvePromise方法來解析回調函數的結果,如果回調函數返回一個promise並且狀態還是pending,就在這個promise的then方法中繼續解析這個promise reslove傳過來的值,如果值還是pending狀態的promise就繼續解析,直到不是一個異步promise,而是一個正常值就使用bridgePromise的reslove方法將bridgePromise的狀態改為fulfilled,並調用onFulfilledCallbacks回調數組中的方法,將該值傳入,到此異步操作就銜接上了。

這里很抽象,我們還是以文件順序讀取的場景畫一張圖解釋一下流程:

當執行p.then(f1).then(f2).then(f3)時:

  1. 先執行p.then(f1)返回了一個bridgePromise(p2),並在p的onFulfilledCallbacks回調列表中放入一個回調函數,回調函數負責執行f1並且更新p2的狀態.
  2. 然后.then(f2)時返回了一個bridgePromise(p3),這里注意其實是p2.then(f2),因為p.then(f1)時返回了p2。此時在p2的onFulfilledCallbacks回調列表中放入一個回調函數,回調函數負責執行f2並且更新p3的狀態.
  3. 然后.then(f3)時返回了一個bridgePromise(p4),並在p3的onFulfilledCallbacks回調列表中放入一個回調函數,回調函數負責執行f3並且更新p4的狀態.
    到此,回調關系注冊完了,如圖所示:
    演示
  4. 然后過了一段時間,p里面的異步操作執行完了,讀取到了1.txt的內容,開始執行p的回調函數,回調函數執行f1,打印出1.txt的內容“this is 1.txt”,並將f1的返回值放到resolvePromise中開始解析。resolvePromise一看傳入了一個promise對象,promise是異步的啊,得等着呢,於是就在這個promise對象的then方法中繼續resolvePromise這個promise對象resolve的結果,一看不是promise對象了,而是一個具體值“this is 2.txt”,於是調用bridgePromise(p2)的reslove方法將bridgePromise(p2)的狀態更新為fulfilled,並將“this is 2.txt”傳入p2的回調函數中去執行。
  5. p2的回調開始執行,f2拿到傳過來的“this is 2.txt”參數開始執行,打印出2.txt的內容,並將f2的返回值放到resolvePromise中開始解析,resolvePromise一看傳入了一個promise對象,promise是異步的啊,又得等着呢........后續操作就是不斷的重復4,5步直到結束。

到此,reslove這一條線已經我們已經走通,讓我們看看reject這一條線,reject其實處理起來很簡單:

  1. 首先執行fn及執行注冊的回調時都用try-catch包裹,無論哪里有異常都會進入reject分支。
  2. 一旦代碼進入reject分支直接將bridge promise設為rejected狀態,於是后續都會走reject這個分支,另外如果不傳異常處理的onRejected函數,默認就是使用throw error將錯誤一直往后拋,達到了錯誤冒泡的目的。
  3. 最后可以實現一個catch函數用來接收錯誤。
MyPromise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}

到此,我們已經可以愉快的使用promise.then(f1).then(f2).then(f3).catch(errorLog)來順序讀取文件內容了。

本節代碼

支持串行異步任務代碼

六. 達到Promises/A+規范

其實,到支持串行異步任務這一節,我們寫的promise在功能上已經基本齊全了,但是還不太規范,比如說一些其他情況的判斷等等,這一節我們就比着Promises/A+的規范打磨一下我們寫的promise。如果只是想學習promise的核心實現的,這一節看不懂也沒關系,因為這一節並沒有增加promise的功能,只是使promise更加規范,更加健壯。

目標

使promise達到Promises/A+規范,通過promises-aplus-tests的完整測試

實現

首先來可以了解一下Promises/A+規范:

Promises/A+規范原版

Promises/A+規范中文版

相比上一節代碼,本節代碼除了在resolvePromise函數里增加了幾個其他情況的判斷外,其他函數都沒有修改。完整promise代碼如下:

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

function MyPromise(fn) {
    const self = this;
    self.value = null;
    self.error = null;
    self.status = PENDING;
    self.onFulfilledCallbacks = [];
    self.onRejectedCallbacks = [];

    function resolve(value) {
        if (value instanceof MyPromise) {
            return value.then(resolve, reject);
        }
        if (self.status === PENDING) {
            setTimeout(() => {
                self.status = FULFILLED;
                self.value = value;
                self.onFulfilledCallbacks.forEach((callback) => callback(self.value));
            }, 0)
        }
    }

    function reject(error) {
        if (self.status === PENDING) {
            setTimeout(function() {
                self.status = REJECTED;
                self.error = error;
                self.onRejectedCallbacks.forEach((callback) => callback(self.error));
            }, 0)
        }
    }
    try {
        fn(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

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);
    }
}

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);
                }
            }, 0);
        })
    }
    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);
                }
            }, 0);
        });
    }
    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);
                }
            });
        });
    }
}
MyPromise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
}
// 執行測試用例需要用到的代碼
MyPromise.deferred = function() {
    let defer = {};
    defer.promise = new MyPromise((resolve, reject) => {
        defer.resolve = resolve;
        defer.reject = reject;
    });
    return defer;
}
try {
    module.exports = MyPromise
} catch (e) {}

我們可以先跑一下測試,需要安裝一下測試插件,然后執行測試,測試時注意在加上上面最后的那幾行代碼才能執行測試用例。

1.npm i -g promises-aplus-tests
2.promises-aplus-tests mypromise.js

運行測試用例可以看到,我們上面寫的promise代碼通過了完整的Promises/A+規范測試。

測試報告
先撒花高興一下~✿✿ヽ(°▽°)ノ✿

然后開始分析我們這一節的代碼,我們主要在resolvePromise里加了額外的兩個判斷,第一個是x和bridgePromise是指向相同值時,報出循環引用的錯誤,使promise符合2.3.1規范,然后我們增加了一個x 為對象或者函數的判斷,這一條判斷主要對應2.3.3規范,中文規范如圖:

中文規范
這一條標准對應的其實是thenable對象,什么是thenable對象,只要有then方法就是thenable對象,然后我們實現的時候照着規范實現就可以了。

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);
        }
    }

再寫完這個分支的代碼后,其實我們已經可以刪除if (x instanceof MyPromise) {}這個分支的代碼,因為promise也是一個thenable對象,完全可以使用上述代碼兼容代替。另外,本節代碼很多重復代碼可以封裝優化一下,但是為了看得清晰,並沒有進行抽象封裝,大家如果覺得重復代碼太多的話,可以自行抽象封裝。

本節代碼

達到Promises/A+規范代碼

七. 實現 promise 的all,race,resolve,reject方法

上一節我們已經實現了一個符合Promises/A+規范的promise,本節我們把一些es6 promise里的常用方法實現一下。

目標

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

實現

我們還是在之前的基礎上繼續往下寫:

MyPromise.all = function(promises) {
    return new MyPromise(function(resolve, reject) {
        let result = [];
        let count = 0;
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(function(data) {
                result[i] = data;
                if (++count == promises.length) {
                    resolve(result);
                }
            }, function(error) {
                reject(error);
            });
        }
    });
}

MyPromise.race = function(promises) {
    return new MyPromise(function(resolve, reject) {
        for (let i = 0; i < promises.length; i++) {
            promises[i].then(function(data) {
                resolve(data);
            }, function(error) {
                reject(error);
            });
        }
    });
}

MyPromise.resolve = function(value) {
    return new MyPromise(resolve => {
        resolve(value);
    });
}

MyPromise.reject = function(error) {
    return new MyPromise((resolve, reject) => {
        reject(error);
    });
}

其實前幾節把promise的主線邏輯實現后,這些方法都不難實現,all的原理就是返回一個promise,在這個promise中給所有傳入的promise的then方法中都注冊上回調,回調成功了就把值放到結果數組中,所有回調都成功了就讓返回的這個promise去reslove,把結果數組返回出去,race和all大同小異,只不過它不會等所有promise都成功,而是誰快就把誰返回出去,resolve和reject的邏輯也很簡單,看一下就明白了。

本節代碼

實現all,race,resolve,reject方法代碼

八. 實現 promiseify 方法

其實到上一節為止,promise的方法已經都講完了,這一節講一個著名promise庫bluebird里面的方法promiseify,因為這個方法很常用而且以前面試還被問過。promiseify有什么作用呢?它的作用就是將異步回調函數api轉換為promise形式,比如下面這個,對fs.readFile 執行promiseify后,就可以直接用promise的方式去調用讀取文件的方法了,是不是很強大。

let Promise = require('./bluebird');
let fs = require("fs");

var readFile = Promise.promisify(fs.readFile);
readFile("1.txt", "utf8").then(function(data) {
    console.log(data);
})

目標

實現bluebird的promiseify方法

實現

MyPromise.promisify = function(fn) {
    return function() {
        var args = Array.from(arguments);
        return new MyPromise(function(resolve, reject) {
            fn.apply(null, args.concat(function(err) {
                err ? reject(err) : resolve(arguments[1])
            }));
        })
    }
}

雖然方法很強大,但是實現起來並沒有很難,想在外邊直接調用promise的方法那就返回一個promise唄,內部將原來參數后面拼接一個回調函數參數,在回調函數里執行這個promise的reslove方法把結果傳出去,promiseify就實現了。

本節代碼

實現promiseify方法

最后

不知不覺寫了這么多了,大家如果覺得還可以就給個贊唄,另外每一節的代碼都托管到了github上,大家可以對照看那一節的promise實現代碼及測試代碼,也順便求個star~

項目地址:本文代碼的github倉庫

另外,實現一個符合Promises/A+規范的promise不止本文一種實現方式,本文只是選取了一種比較通俗易懂的實現方式作為講解,大家也可以用自己的方式去實現一個符合Promises/A+規范的promise。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM