從頭手寫一個Promise

前言

在javascript的世界中,全部代碼都是單線程執行的。因爲這個「缺陷」,致使JavaScript的全部網絡操做,瀏覽器事件,都必須是異步執行。 最開始咱們能夠用回調函數來解決這個問題,javascript

function callBack(){
console.log('回調')
}
setTimeout(callBack, 1000)
// 回調
複製代碼

可是隨着業務的不斷深刻,不免會像陷入回調地獄這樣的問題。直到後來咱們有了Promise來解決這個問題。html

手寫一個promise

promise的基本用法以下: 在實例化一個Promise時,傳入一個函數做爲參數,該函數接受兩個參數,分別爲resolve,reject.如解決則會打印數據,如被拒絕則會打印拒絕緣由java

let p1 = new Promise(function (resolve, reject) {

})
p1.then(function (data) {
    console.log(data)
}, function (err) {
    console.log(err)
})
複製代碼

相關概念

  1. 術語
  • 解決(fulfill):指一個 promise 成功時進行的一系列操做,如狀態的改變、回調的執行。雖然規範中用 fulfill 來表示解決,但在後世的 promise 實現多以 resolve 來指代之。
  • 拒絕(reject):指一個 promise 失敗時進行的一系列操做。
  • 終值(eventual value):所謂終值,指的是 promise 被解決時傳遞給解決回調的值,因爲 promise 有一次性的特徵,所以當這個值被傳遞時,標誌着 promise 等待態的結束,故稱之終值,有時也直接簡稱爲值(value)。
  • 拒因(reason):也就是拒絕緣由,指在 promise 被拒絕時傳遞給拒絕回調的值
  1. 執行流程
    image
    每一個promise後面鏈一個對象該對象包含onfulfiled,onrejected,子promise三個屬性,當父promise 狀態改變完畢,執行完相應的onfulfiled/onfulfiled的時候呢,拿到子promise,在等待這個子promise狀態改變,再執行相應的onfulfiled/onfulfiled。依次循環直到當前promise沒有子promise

3.狀態機制切換 如圖所示,狀態只能由pengding-->fulfilled,或者由pending-->rejected這樣轉變。 只要這兩種狀況發生,狀態就凝固了,不會再變了,會一直保持這個結果。就算改變已經發生了,你再對Promise對象添加回調函數,也會當即獲得這個結果。這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的 數組

image

基本結構

function Promise(executor) {
            this.state = 'pending'; //狀態
            this.value = undefined; //成功結果
            this.reason = undefined; //失敗緣由
            function resolve(value) { }
            function reject(reason) { }
            executor(resolve, reject) //當即執行
        }
複製代碼

接收一個executor函數,executor函數傳入就執行(當咱們示例化一個promise時,executor當即執行),執行完同步或異步操做後,調用它的兩個參數resolve和reject。其中state保存了promise的狀態,包含三個狀態:等待態(pending)成功態(resolved)和失敗態(rejected)。promise執行成功後的結果由value保存,失敗後的緣由由reason保存。promise

完善resolve與reject

  • new Promise((resolve, reject)=>{resolve(value)}) resolve爲成功,接收參數value,狀態改變爲fulfilled,不可再次改變。
  • new Promise((resolve, reject)=>{reject(reason)}) reject爲失敗,接收參數reason,狀態改變爲rejected,不可再次改變。
  • 如果executor函數報錯 直接執行reject() 咱們能夠這樣實現:
function Promise(executor) {
            this.state = 'pending'; //狀態
            this.value = undefined; //成功結果
            this.reason = undefined; //失敗緣由
            resolve = (value) => {
                // state改變,resolve調用就會失敗
                if (this.state === 'pending') {
                    // resolve調用後,state轉化爲成功態
                    this.state = 'fulfilled';
                    // 儲存成功的值
                    this.value = value;
                }
            }
            reject = (reason) => {
                // state改變,reject調用就會失敗
                if (this.state === 'pending') {
                    // reject調用後,state轉化爲失敗態
                    this.state = 'rejected';
                    // 儲存失敗的緣由
                    this.reason = reason;
                }
            }
            //若是executor執行報錯,直接執行reject
            try {
                executor(resolve, reject)
            } catch (err) {
                reject(err)  // executor出錯就直接調用
            }
        }
複製代碼

實現then方法

每個Promise實例都有一個then方法,接收兩個爲函數的參數,它用來處理異步返回的結果,它是定義在原型上的方法。瀏覽器

Promise.prototype.then = function (onFulfilled, onRejected) {
        };
複製代碼

當promise的狀態發生了變化,不論成功或失敗都會調用then方法,所以then方法裏面也會根據不一樣的狀態來判斷調用哪個回調函數。 兩個參數的注意事項:網絡

  • onFulfilled 和 onRejected 都是可選參數,也就是說能夠傳也能夠不傳。傳入的回調函數如不是一個函數類型,能夠直接忽略。
  • 兩個參數在 promise 執行結束前其不可被調用,其調用次數不可超過一次。
Promise.prototype.then = function (onFulfilled, onRejected) {
            if (this.state === 'fulfilled') {
                //判斷參數類型,是函數執行之,若是 onFulfilled 不是函數,其必須被忽略
                if (typeof onFulfilled === 'function') { 
                    onFulfilled(this.value);  // 傳入成功的值
                }
            }
           // 若是 onRejected 不是函數,其必須被忽略
            if (this.state === 'rejected') {
                if (typeof onRejected === 'function') { 
                    onRejected(this.reason); // 傳入失敗的緣由
                }
            }
        };
複製代碼

支持異步實現

上述把promise的基本功能都實現了,可是仍是會存在一個問題,就是promise不支持異步代碼,當resolve或reject在setTimeout中實現時,調用then方法時,此時狀態仍然是pengding,then方法即沒有調用onFulfilled也沒有調用onRejected,也就運行沒有任何結果。異步

咱們能夠參照發佈訂閱模式,在執行then方法時狀態仍是狀態仍是pengding時,把回調函數存儲在一個數組中,當狀態發生改變時依次從數組中取出執行就行了,首先在類上新增兩個Array類型的數組,用於存放回調函數。函數

function Promise(executor) {
            this.state = 'pending'; //狀態
            this.value = undefined; //成功結果
            this.reason = undefined; //失敗緣由
            this.onFulfilledFunc = [];//保存成功回調
            this.onRejectedFunc = [];//保存失敗回調
            function resolve(value) { 
              // ....
            }
            function reject(reason) { 
              // ....
            }
            executor(resolve, reject) //當即執行
        }
複製代碼

並修改then方法post

Promise.prototype.then = function (onFulfilled, onRejected) {
            if (this.state === 'pending') {
                if (typeof onFulfilled === 'function') {
                    this.onFulfilledFunc.push(onFulfilled);//保存回調
                }
                if (typeof onRejected === 'function') {
                    this.onRejectedFunc.push(onRejected);//保存回調
                }
            }
            if (this.state === 'fulfilled') {
                //判斷參數類型,是函數執行之,若是 onFulfilled 不是函數,其必須被忽略
                if (typeof onFulfilled === 'function') {
                    onFulfilled(this.value);  // 傳入成功的值
                }
            }
            // 若是 onRejected 不是函數,其必須被忽略
            if (this.state === 'rejected') {
                if (typeof onRejected === 'function') {
                    onRejected(this.reason); // 傳入失敗的緣由
                }
            }
        };
複製代碼

修改resolve和reject方法:

function Promise(executor) {
           // 其餘代碼
            function resolve(value) {
                // state改變,resolve調用就會失敗
                if (this.state === 'pending') {
                    // resolve調用後,state轉化爲成功態
                    this.state = 'fulfilled';
                    // 儲存成功的值
                    this.value = value;
                    this.onFulfilledFunc.forEach(fn => fn(value))
                }
            }
            function reject(reason) {
                // state改變,reject調用就會失敗
                if (this.state === 'pending') {
                    // reject調用後,state轉化爲失敗態
                    this.state = 'rejected';
                    // 儲存失敗的緣由
                    this.reason = reason;
                    this.onRejectedFunc.forEach(fn => fn(reason))
                }
            }
           // 其餘代碼
        }
複製代碼

到這裏Promise已經支持了異步操做了。

鏈式調用實現

光是實現了異步操做可不行,咱們經常用到new Promise().then().then()這樣的鏈式調用來解決回調地獄。 規範如何定義then方法:

  • 每一個then方法都返回一個新的Promise對象(原理的核心)
  • 若是then方法中顯示地返回了一個Promise對象就以此對象爲準,返回它的結果
  • 若是then方法中返回的是一個普通值(如Number、String等)就使用此值包裝成一個新的Promise對象返回。
  • 若是then方法中沒有return語句,就視爲返回一個用Undefined包裝的Promise對象
  • 若then方法中出現異常,則調用失敗態方法(reject)跳轉到下一個then的onRejected
  • 若是then方法沒有傳入任何回調,則繼續向下傳遞(值的傳遞特性) 總的來講就是不論什麼時候then方法都要返回一個Promise,這樣才能調用下一個then方法。咱們能夠實例化一個promise2返回,將這個promise2返回的值傳遞到下一個then中。
Promise.prototype.then = function (onFulfilled, onRejected) {
    let promise2 = new Promise((resolve, reject) => {
    // 其餘代碼
    }
    return promise2;
};
複製代碼

接下來就處理根據上一個then方法的返回值來生成新Promise對象.

/** * 解析then返回值與新Promise對象 * @param {Object} promise2 新的Promise對象 * @param {*} x 上一個then的返回值 * @param {Function} resolve promise2的resolve * @param {Function} reject promise2的reject */
function resolvePromise(promise2, x, resolve, reject) {
    //...
}
複製代碼

當then的返回值與新生成的Promise對象爲同一個(引用地址相同),狀態永遠爲等待態(pending),再也沒法成爲resolved或是rejected,程序會死掉,則會拋出TypeError錯誤

let promise2 = p.then(data => {
    return promise2;
});
// TypeError: Chaining cycle detected for promise #<Promise>
複製代碼

所以須要判斷x。

  1. x不能和新生成的promise對象爲同一個
  2. x 不能是null,能夠是對象或者函數(包括promise), 不然是普通值,那麼直接resolve(x)
  3. 當x是對象或者函數(默認promise)則聲明then,let then = x.then
  4. 若是取then報錯,則走reject()
  5. 若是then是個函數,則用call執行then,第一個參數是this,後面是成功的回調和失敗的回調,成功和失敗只能調用一個 因此設定一個called來防止屢次調用
  6. 若是成功的回調仍是pormise,就遞歸繼續解析

小提示: 爲何取對象上的屬性有報錯的可能?Promise有不少實現(bluebird,Q等),Promises/A+只是一個規範,你們都按此規範來實現Promise纔有可能通用,所以全部出錯的可能都要考慮到,假設另外一我的實現的Promise對象使用Object.defineProperty()惡意的在取值時拋錯,咱們能夠防止代碼出現Bug resolvePromise實現

function resolvePromise(promise2, x, resolve, reject) {
            if (promise2 === x) {  // 1.x不能等於promise2
                reject(new TypeError('Promise發生了循環引用'));
            }
            let called;
            if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
                // 2. 多是個對象或是函數
                try {
                    let then = x.then;// 3.取出then方法引用
                    if (typeof then === 'function') {  // 此時認爲then是一個Promise對象
                        //then是function,那麼執行Promise
                        then.call(x, (y) => {   // 5.使用x做爲this來調用then方法,即then裏面的this指向x
                            if (called) return;
                            called = true;
                            // 6.遞歸調用,傳入y如果Promise對象,繼續循環
                            resolvePromise(promise2, y, resolve, reject);
                        }, (r) => {
                            if (called) return;
                            called = true;
                            reject(r);
                        });
                    } else {
                        resolve(x);
                    }
                } catch (e) {
                    // 也屬於失敗
                    if (called) return;
                    called = true;
                    reject(e); // 4.取then報錯,直接reject
                }

            } else {
                //不然是個普通值
                resolve(x);
            }
        }
複製代碼

此時鏈式調用支持已經實現,在相應的地方調用resolvePromise方法便可。

最後完善

規範還對onFulfilled和onRejected有規定

  • onFulfilled返回一個普通的值,成功時直接等於 value => value
  • onRejected返回一個普通的值,失敗時若是直接等於 value => value,則會跑到下一個then中的onFulfilled中,因此直接扔出一個錯誤reason => throw err
  • onFulfilled或onRejected不能同步被調用,必須異步調用。咱們就用setTimeout解決異步問題
  • 若是onFulfilled或onRejected報錯,則直接返回reject()

完善then方法

Promise.prototype.then = function (onFulfilled, onRejected) {
            let promise2 = new Promise((resolve, reject) => {
                // onFulfilled若是不是函數,就忽略onFulfilled,直接返回value
                onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
                // onRejected若是不是函數,就忽略onRejected,直接扔出錯誤
                onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
                if (this.state === 'pending') {
                    this.onFulfilledFunc.push(() => {
                        // 異步
                        setTimeout(() => {
                            try {
                                let x = onFulfilled(this.value);
                                resolvePromise(promise2, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    })
                    this.onRejectedFunc.push(() => {
                        // 異步
                        setTimeout(() => {
                            try {
                                let x = onRejected(this.value);
                                resolvePromise(promise2, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    })
                }
                if (this.state === 'fulfilled') {
                    // 異步
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
                if (this.state === 'rejected') {
                    // 異步
                    setTimeout(() => {
                        // 若是報錯
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
            })
            return promise2;
        };
複製代碼

到這裏手寫一個Promise已經所有實現了 完整代碼

function Promise(executor) {
            this.state = 'pending'; //狀態
            this.value = undefined; //成功結果
            this.reason = undefined; //失敗緣由
            this.onFulfilledFunc = [];//保存成功回調
            this.onRejectedFunc = [];//保存失敗回調
            resolve = (value) => {
                // state改變,resolve調用就會失敗
                if (this.state === 'pending') {
                    // resolve調用後,state轉化爲成功態
                    this.state = 'fulfilled';
                    // 儲存成功的值
                    this.value = value;
                    this.onFulfilledFunc.forEach(fn => fn(value))
                }
            }
            reject = (reason) => {
                // state改變,reject調用就會失敗
                if (this.state === 'pending') {
                    // reject調用後,state轉化爲失敗態
                    this.state = 'rejected';
                    // 儲存失敗的緣由
                    this.reason = reason;
                    this.onRejectedFunc.forEach(fn => fn(reason))
                }
            }
            //若是executor執行報錯,直接執行reject
            try {
                executor(resolve, reject)
            } catch (err) {
                reject(err)  // executor出錯就直接調用
            }
        }
        Promise.prototype.then = function (onFulfilled, onRejected) {
            let promise2 = new Promise((resolve, reject) => {
                // onFulfilled若是不是函數,就忽略onFulfilled,直接返回value
                onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
                // onRejected若是不是函數,就忽略onRejected,直接扔出錯誤
                onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
                if (this.state === 'pending') {
                    this.onFulfilledFunc.push(() => {
                        // 異步
                        setTimeout(() => {
                            try {
                                let x = onFulfilled(this.value);
                                resolvePromise(promise2, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    })
                    this.onRejectedFunc.push(() => {
                        // 異步
                        setTimeout(() => {
                            try {
                                let x = onRejected(this.value);
                                resolvePromise(promise2, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    })
                }
                if (this.state === 'fulfilled') {
                    // 異步
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
                if (this.state === 'rejected') {
                    // 異步
                    setTimeout(() => {
                        // 若是報錯
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                }
            })
            return promise2;
        };
        function resolvePromise(promise2, x, resolve, reject) {
            if (promise2 === x) {
                reject(new TypeError('Promise發生了循環引用'));
            }
            let called;
            if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
                //多是個對象或是函數
                try {
                    let then = x.then;//取出then方法引用
                    if (typeof then === 'function') {  // 認爲then是一個Promise對象
                        //then是function,那麼執行Promise
                        then.call(x, (y) => {
                            // 成功和失敗只能調用一個
                            if (called) return;
                            called = true;
                            //遞歸調用,傳入y如果Promise對象,繼續循環
                            resolvePromise(promise2, y, resolve, reject);
                        }, (r) => {
                            // 成功和失敗只能調用一個
                            if (called) return;
                            called = true;
                            reject(r);
                        });
                    } else {
                        resolve(x);
                    }
                } catch (e) {
                    // 也屬於失敗
                    if (called) return;
                    called = true;
                    reject(e);
                }

            } else {
                //不然是個普通值
                resolve(x);
            }
        }
複製代碼

可是隻用構造函數實現固然是不夠的,咱們再用class來實現一個Promise,基本原理同上 class實現

class Promise {
            constructor(executor) {
                this.state = 'pending';
                this.value = undefined;
                this.reason = undefined;
                this.onResolvedCallbacks = [];
                this.onRejectedCallbacks = [];
                let resolve = value => {
                    if (this.state === 'pending') {
                        this.state = 'fulfilled';
                        this.value = value;
                        this.onResolvedCallbacks.forEach(fn => fn());
                    }
                };
                let reject = reason => {
                    if (this.state === 'pending') {
                        this.state = 'rejected';
                        this.reason = reason;
                        this.onRejectedCallbacks.forEach(fn => fn());
                    }
                };
                try {
                    executor(resolve, reject);
                } catch (err) {
                    reject(err);
                }
            }
            then(onFulfilled, onRejected) {
                onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
                onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
                let promise2 = new Promise((resolve, reject) => {
                    if (this.state === 'fulfilled') {
                        setTimeout(() => {
                            try {
                                let x = onFulfilled(this.value);
                                resolvePromise(promise2, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    };
                    if (this.state === 'rejected') {
                        setTimeout(() => {
                            try {
                                let x = onRejected(this.reason);
                                resolvePromise(promise2, x, resolve, reject);
                            } catch (e) {
                                reject(e);
                            }
                        }, 0);
                    };
                    if (this.state === 'pending') {
                        this.onResolvedCallbacks.push(() => {
                            setTimeout(() => {
                                try {
                                    let x = onFulfilled(this.value);
                                    resolvePromise(promise2, x, resolve, reject);
                                } catch (e) {
                                    reject(e);
                                }
                            }, 0);
                        });
                        this.onRejectedCallbacks.push(() => {
                            setTimeout(() => {
                                try {
                                    let x = onRejected(this.reason);
                                    resolvePromise(promise2, x, resolve, reject);
                                } catch (e) {
                                    reject(e);
                                }
                            }, 0)
                        });
                    };
                });
                return promise2;
            }
            catch(fn) {
                return this.then(null, fn);
            }
        }
        function resolvePromise(promise2, x, resolve, reject) {
            if (x === promise2) {
                return reject(new TypeError('Chaining cycle detected for promise'));
            }
            let called;
            if (x != null && (typeof x === 'object' || typeof x === 'function')) {
                try {
                    let then = x.then;
                    if (typeof then === 'function') {
                        then.call(x, y => {
                            if (called) return;
                            called = true;
                            resolvePromise(promise2, y, resolve, reject);
                        }, err => {
                            if (called) return;
                            called = true;
                            reject(err);
                        })
                    } else {
                        resolve(x);
                    }
                } catch (e) {
                    if (called) return;
                    called = true;
                    reject(e);
                }
            } else {
                resolve(x);
            }
        }
        //resolve方法
        Promise.resolve = function (val) {
            return new Promise((resolve, reject) => {
                resolve(val)
            });
        }
        //reject方法
        Promise.reject = function (val) {
            return new Promise((resolve, reject) => {
                reject(val)
            });
        }
        //race方法 
        Promise.race = function (promises) {
            return new Promise((resolve, reject) => {
                for (let i = 0; i < promises.length; i++) {
                    promises[i].then(resolve, reject)
                };
            })
        }
        //all方法(獲取全部的promise,都執行then,把結果放到數組,一塊兒返回)
        Promise.all = function (promises) {
            let arr = [];
            let i = 0;
            function processData(index, data) {
                arr[index] = data;
                i++;
                if (i == promises.length) {
                    resolve(arr);
                };
            };
            return new Promise((resolve, reject) => {
                for (let i = 0; i < promises.length; i++) {
                    promises[i].then(data => {
                        processData(i, data);
                    }, reject);
                };
            });
        }
複製代碼

最終測試

開源社區提供了一個包用於測試咱們的代碼:promises-aplus-tests,安裝這個包而後運行命令行 promises-aplus-tests [js文件名] 便可驗證。別忘了再代碼後面加上這一段代碼

// 目前是經過他測試 他會測試一個對象
// 語法糖
Promise.defer = Promise.deferred = function () {
  let dfd = {}
  dfd.promise = new Promise((resolve,reject)=>{
    dfd.resolve = resolve;
    dfd.reject = reject;
  });
  return dfd;
}
module.exports = Promise;
複製代碼

參考連接

相關文章
相關標籤/搜索