Promise 詳解

Promise

含義

Promise是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更合合理、強大。所謂Promise,簡單來說就是一個容器,裏面保存着某個未來纔會結束的事件(通常是一個異步操作)的結果。從語法上說,Promies是一個對象,從它可以獲取異步操作的消息。Promise提供統一的API,各種異步操作都可以用同樣的方法進行處理

Promise對象有以下兩個特點

  • 對象的狀態不受外界影響。
  • 一旦狀態改變,就不會再變,任何時候都可以得到這個結果

promise有三種狀態,pending(進行中)、fulfilled/resolved(已成功)和rejected(已失敗)

Promise優點

  • 將異步操作以同步操作的流程表達出來,避免了回調地獄
  • Promise對象提供統一的接口,更加容易控制異步操作

Promise缺點

  • 無法中途終止,一旦新建立即執行
  • 如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部
  • 當處於pending狀態時,無法得知目前進展到哪一個階段

Promise設計原理

Promise的實現過程,其主要使用了設計模式中的觀察者模式:

  • 通過Promise.prototype.then和Promise.prototype.catch方法將觀察者方法註冊到被觀察者Promise對象中,同時返回一個新的Promise對象,以便可以鏈式調用。
  • 被觀察者管理內部pending、fulfilled和rejected的狀態轉變,同時通過構造函數中傳遞的resolve和reject方法以主動觸發狀態轉變和通知觀察者。

Promise 是通過.then方法來實現多個異步操作的順序執行
Promise的內部也有一個 defers 隊列存放事件,而 .then 方法的作用和發佈訂閱模式的on方法一樣是用來訂閱事件的,每次調用 .then 方法就會往defers隊列中放入一個事件,當異步操作完成時, resolve方法標示前一個異步過程完成並從defers隊列中取出第一個事件執行並返回當前對象保證鏈式調用,以此類推,就完成了所有異步過程的隊列執行

用法

實例化Promise,返回Promise對象
const promise = new Promise(function(resolve, reject) {
  // ... some code
  if (/* 異步操作成功 */){
    resolve(value);
  } else {
    reject(error);
  }
});

promise.then(function(value){
  // success
}, function(err){
  // failure
})

Promise對象是一個構造函數,用來生成Promise實例。通過new返回一個Promise實例
Promise構造函數接受一個函數作爲參數,該函數接受resolve和reject兩個回調函數作爲參數,它們由 JavaScript 引擎提供,不用自己部署,用來改變Promise的狀態

  • resolve函數,將Promise對象的狀態從 pending => resolved,在異步操作成功時調用,並將異步操作的結果,作爲參數傳遞出去;
  • reject函數,將Promise對象的狀態從 pending => rejected,在異步操作失敗時調用,並將異步操作報出的錯誤,作爲參數傳遞出去

Promise.prototype.then

then是Promise構造函數原型鏈上的方法,Promise.prototype.then,then方法分別指定resolved狀態和rejected狀態的回調函數

then方法可以接受兩個回調函數作爲參數。第一個回調函數是Promise對象的狀態變爲resolved時調用,第二個回調函數是Promise對象的狀態變爲rejected時調用【可選】

Promise 實現

主要實現

第一步,初步構建。Promise 的參數是函數 executor,把內部定義 resolve 方法作爲參數傳到 executor 中,調用 executor。當異步操作成功後會調用 resolve 方法,然後就會執行 then 中註冊的回調函數;並通過then中的return this實現鏈式調用

        function MyPromise(executor) {
            var value = null,
                callbacks = [];  //callbacks爲數組,因爲可能同時有很多個回調

            this.then = function (onFulfilled) {
                callbacks.push(onFulfilled);
                return this // 返回Promise對象,實現鏈式調用
            };

            // 成功回調
            function resolve(value) {
                setTimeout(function() { // Promises/A+規範明確要求回調需要通過異步方式執行,用以保證一致可靠的執行順序;通過setTimeout機制,將resolve中執行回調的邏輯放置到JS任務隊列末尾,以保證在resolve執行時,then方法的回調函數已經註冊完成
                    callbacks.forEach(function (callback) {
                        callback(value);
                    });
                }, 0)
            }

            executor(resolve);
        }

第二步,添加狀態state

        function MyPromise(executor) {
            var state = 'pending',
                value = null,
                callbacks = [];

            this.then = function (onFulfilled) {
                if (state === 'pending') {
                    callbacks.push(onFulfilled);
                    return this;
                }
                onFulfilled(value);
                return this;
            };

            function resolve(newValue) {
                value = newValue;
                state = 'fulfilled';
                setTimeout(function () {
                    callbacks.forEach(function (callback) {
                        callback(value);
                    });
                }, 0);
            }

            executor(resolve);
        }

完整實現代碼

        function MyPromise(executor) {
            this._status = 'PENDING'
            this._value = null
            this._reason = null
            this._resolveFns = []
            this._rejectFns = []

            var handler = function(state, value) {
                if (this.state !== 'PENDING') return 

                let promise = this
                let fns = []

                setTimeout(function() {
                    promise.state = state 
                    let st = state === 'FULFILLED'
                    fns = st ? promise['_resolveFns'] : promise['_resolveFns']
                    fns.forEach(fn => {
                        value = fn.call(promise, value) || value
                    })

                    promise[st ? '_value' : '_reason'] = value;
                    promise['_resolveFns'] = promise['_resolveFns'] = undefined;
                }, 0)
            }

            var resolve = function(value) {
                handler.call(this, 'FULFILLED', value);
            }

            var reject = function(reason){
                handler.call(this, 'REJECTED', reason);
            }
            
            executor(resolve,reject)
        }


        MyPromise.prototype.then = function(resolveFn, rejectFn) {
            var promise = this
            //串行
            return new Promise(function(resolveNext, rejectNext) {
                // 在 then 方法,對then方法的回調函數返回結果 ret 進行判斷,如果是一個 promise 對象,就會調用其 then 方法,形成一個嵌套,直到其不是promise對象爲止
                function resolveHandler(value) {
                    var result = typeof resolveFn === 'function' && resolveFn(value) || value 
                    if (result && typeof result.then === 'function') {
                        result.then(function(value) {
                            resolveNext(value)
                        }, function(reason) {
                            rejectNext(reason)
                        })
                    } else {
                        resolveNext(result)
                    }
                }

                function rejectHandler(reason) {
                    var reason = typeof rejectFn === 'function' && rejectFn(reason) || reason 
                    rejectNext(reason)
                }

                if(promise._status === 'PENDING'){
                    promise._resolveFns.push(resolveHandler);
                    promise._rejectFns.push(rejectHandler);
                }else if(promise._status === 'FULFILLED'){ // 狀態改變後的then操作,立刻執行
                    resolveHandler(promise._value);
                }else if(promise._status === 'REJECTED'){
                    rejectHandler(promise._reason);
                }
                
            })
        }

測試

        // 實際上,fn中的resolve、reject函數分別是then方法中對應的函數參數
        var fn5 = function(resolve, reject){
          console.log('oooo1')
          resolve('5');
          console.log('ssss1')
        }
        var fn6 = function(resolve, reject){
          resolve('6');
        }

        new MyPromise(fn5).then(function(data){
          console.log(data);
          return new MyPromise(fn6);
        }).then(function(data){
          console.log(data);
        });

其他常用 Promise API 實現

MyPromise.prototype.catch = function(reject) {
            return this.then(undefined, reject)
        }

        MyPromise.prototype.delay = function(time) {
            return this.then(function(ori){
                return Promise.delay(ms, value || ori);
            })
        }

        MyPromise.delay = function(ms, value) {
            return new MyPromise(function(resolve, reject){
                setTimeout(function(){
                    resolve(value)
                }, ms)
            })
        }

        MyPromise.resolve = function(value) {
            return new MyPromise(function(resolve, reject){
                resolve(value)
            })
        }

        MyPromise.reject = function(value) {
            return new MyPromise(function(resolve, reject){
                reject(value)
            })
        }

        MyPromise.all = function(promises) {
            return new MyPromise(function(resolve, reject) {
                var result = []
                function resolver(index) {
                    return function(val) {
                        result.push(val)
                        while(--index) {
                            resolve(result)
                        }
                    }
                }

                function rejecter(reason) {
                    reject(reason)
                }

                for (var i = 0; i < promises.length; i++) {
                    promises[i].then(resolver(i), rejecter)
                }
            })
        }

        MyPromise.race = function(promises) {
            return new MyPromise(resolve, reject) {
                function resolver(val) {
                    resolve(val)
                }

                function rejecter(reason) {
                    reject(reason)
                }

                for (var i = 0; i < promises.length; i++) {
                    promises[i].then(resolver, rejecter)
                }
            }
        }

遵循Promise A+ 規範的 Promise 實現

Promise A+ then 規範 Promise決議解決過程

    這個是將promise和一個值x作爲輸入的一個抽象操作。如果這個x是支持then的,他會嘗試讓promise接受x的狀態;否則,他會用x的值來fullfill這個promise。運行這樣一個東西,遵循以下的步驟
        2.3.1 如果promise和x指向同一個對象,則reject這個promise使用TypeError。
        2.3.2 如果x是一個promise,接受他的狀態
            2.3.2.1 如果x在pending,promise必須等待x的狀態改變
            2.3.2.2 如果x被fullfill,那麼fullfill這個promise使用同一個value
            2.3.2.3 如果x被reject,那麼reject這個promise使用同一個理由

        2.3.3 如果x是一個對象或者是個方法
            2.3.3.1 then = x.then
            2.3.3.2 如果x.then返回了錯誤,則reject這個promise使用錯誤。
            2.3.3.3 如果then是一個function,使用x爲this,resolvePromise爲一參,rejectPromise爲二參,
                2.3.3.3.1 如果resolvePromise被一個值y調用,那麼運行[[Resolve]](promise, y)
                2.3.3.3.2 如果rejectPromise被reason r,使用r來reject這個promise
                2.3.3.3.3 如果resolvePromise和rejectPromise都被調用了,那麼第一個被調用的有優先權,其他的beihulue
                2.3.3.3.4 如果調用then方法得到了exception,如果上面的方法被調用了,則忽略,否則reject這個promise
            2.3.3.4 如果then方法不是function,那麼fullfill這個promise使用x
        2.3.4 如果x不是一個對象或者方法,那麼fullfill這個promise使用x

    如果promise產生了環形的嵌套,比如[[Resolve]](promise, thenable)最終喚起了[[Resolve]](promise, thenable),那麼實現建議且並不強求來發現這種循環,並且reject這個promise使用一個TypeError。

代碼實現

const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
function Promise(executor) {
    let self = this;
    self.status = PENDING;
    self.onFulfilled = [];//成功的回調
    self.onRejected = []; //失敗的回調
    //PromiseA+ 2.1
    function resolve(value) {
        if (self.status === PENDING) {
            self.status = FULFILLED;
            self.value = value;
            self.onFulfilled.forEach(fn => fn());//PromiseA+ 2.2.6.1
        }
    }

    function reject(reason) {
        if (self.status === PENDING) {
            self.status = REJECTED;
            self.reason = reason;
            self.onRejected.forEach(fn => fn());//PromiseA+ 2.2.6.2
        }
    }

    try {
        executor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

Promise.prototype.then = function (onFulfilled, onRejected) {
    //PromiseA+ 2.2.1 / PromiseA+ 2.2.5 / PromiseA+ 2.2.7.3 / PromiseA+ 2.2.7.4
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };
    let self = this;
    //PromiseA+ 2.2.7
    let promise2 = new Promise((resolve, reject) => {
        if (self.status === FULFILLED) {
            //PromiseA+ 2.2.2
            //PromiseA+ 2.2.4 --- setTimeout
            setTimeout(() => {
                try {
                    //PromiseA+ 2.2.7.1
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    //PromiseA+ 2.2.7.2
                    reject(e);
                }
            });
        } else if (self.status === REJECTED) {
            //PromiseA+ 2.2.3
            setTimeout(() => {
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        } else if (self.status === PENDING) {
            self.onFulfilled.push(() => {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(self.value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            });
            self.onRejected.push(() => {
                setTimeout(() => {
                    try {
                        let x = onRejected(self.reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            });
        }
    });
    return promise2;
}

        
function resolvePromise(promise2, x, resolve, reject) {
    let self = this;
    //PromiseA+ 2.3.1
    if (promise2 === x) {
        reject(new TypeError('Chaining cycle'));
    }
    if (x && typeof x === 'object' || typeof x === 'function') {
        let used; //PromiseA+2.3.3.3.3 只能調用一次
        try {
            let then = x.then;
            if (typeof then === 'function') {
                //PromiseA+2.3.3
                then.call(x, (y) => {
                    //PromiseA+2.3.3.1
                    if (used) return;
                    used = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, (r) => {
                    //PromiseA+2.3.3.2
                    if (used) return;
                    used = true;
                    reject(r);
                });

            }else{
                //PromiseA+2.3.3.4
                if (used) return;
                used = true;
                resolve(x);
            }
        } catch (e) {
            //PromiseA+ 2.3.3.2
            if (used) return;
            used = true;
            reject(e);
        }
    } else {
        //PromiseA+ 2.3.3.4
        resolve(x);
    }
}

module.exports = Promise;

resolvePromise 函數即爲根據x的值來決定promise2的狀態的函數,也即標準中的Promise Resolution Procedure;x爲promise2 = promise1.then(onResolved, onRejected)onResolved/onRejected的返回值,resolvereject實際上是promise2executor的兩個實參,因爲很難掛在其它的地方,所以一併傳進來。

promise 總結

  1. Promise的then方法回調是異步,構造函數內是同步。【內同外異】
  2. Promise狀態一旦改變,無法在發生變更。 pending -> fulfilled、 pending -> rejected
  3. Promise的then方法的參數期望是函數,傳入非函數則會發生值穿透。
  4. promise 的.then或者.catch可以被調用多次,但這裏 Promise 構造函數只執行一次。或者說 promise 內部狀態一經改變,並且有了一個值,那麼後續每次調用.then 或者.catch都會直接拿到該值。
  5. promise 的.then或者.catch返回promise對象【catch方法是then(null, function(error){})的語法糖/省略寫法】
  6. 如果調用 then 時,promise已經成功,則執行 onFulfilled,並將promise的值作爲參數傳遞進去。如果promise已經失敗,那麼執行 onRejected, 並將 promise 失敗的原因作爲參數傳遞進去。如果promise的狀態是pending,需要將onFulfilled和onRejected函數存放起來,等待狀態確定後,再依次將對應的函數執行(發佈訂閱)
  7. then 的參數 onFulfilled 和 onRejected 可以缺省
  8. promise 可以then多次,promise 的then 方法返回一個 promise
  9. 如果 then 返回的是一個結果,那麼就會把這個結果作爲參數,傳遞給下一個then的成功的回調(onFulfilled)
  10. 如果 then 中拋出了異常,那麼就會把這個異常作爲參數,傳遞給下一個then的失敗的回調(onRejected)
  11. 如果 then 返回的是一個promise,那麼需要等這個promise,那麼會等這個promise執行完,promise如果成功,就走下一個then的成功,如果失敗,就走下一個then的失敗
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章