學習並實現一個Promise

學習自阮一峯老師 ECMAScript 6 入門 Promise介紹
參考 promise-實現 es6

Promise屬於微任務,而模擬的 Promise 都是使用setTimeout,屬於宏任務。因此在某些狀況下會有bug,須要注意,如:ajax

setTimeout('console.log(1)')
new Promise(resolve=>{
    console.log(2)
    resolve()
}).then(_=>{
    console.log(3)
})

正確的結果應該是 2 3 1,可是模擬的 Promise 返回 1 2 3。
bluebird,es6-promise等也有這個問題。數組

代碼實現

// 三種狀態 + 1
var PENDING = "pending";
var RESOLVED = "resolved";
var REJECTED = "rejected";
var FINALLY = "finally";

var Promise = window.Promise || function(){
    var Promise = function(fn){
        var that = this;
        that.currentState = PENDING;
        that.value = undefined;
        // 用於保存 then 中的回調,只有當 promise
        // 狀態爲 pending 時纔會緩存,而且每一個實例至多緩存一個
        that.Callbacks = []
        
        //這個函數是封裝的resolve,reject的公共方法,這個方法不執行,則then、catch、finally中的回調不執行
        var stateF = function(state, value){
            setTimeout(function(){ // 異步執行,保證執行順序
                  if (that.currentState === PENDING) {
                    that.value = value;
                    
                    var cb;
                    //回調狀態,默認爲Promise狀態
                    //經過修改回調狀態,來肯定運行哪一個回調
                    //如沒有拋出錯誤,則不運行 REJECTED 回調;拋出錯誤,則catch以前的 RESOLVED 回調不運行
                    var cbState = that.currentState = state;
                    while(cb = that.Callbacks.shift()){
                        //狀態爲 RESOLVED,運行then回調;狀態爲 REJECTED,運行catch回調
                        if(cb.state == cbState || cb.state == FINALLY){
                            try{
                                if(cb.state == FINALLY){
                                    //狀態爲 finally 的回調不接受參數,不返回參數
                                    cb.fn()
                                }else{
                                    //運行正確,回調狀態變爲 RESOLVED
                                    cbState = RESOLVED
                                    //該回調的返回值是下一個 then 回調的參數
                                    that.value = cb.fn(that.value)
                                }
                            }catch(err){
                                //運行錯誤,回調狀態變爲 REJECTED
                                cbState = REJECTED
                                //該錯誤是下一個 catch 回調的參數
                                that.value = err
                            }
                        }
                    }
                  }
            })
        }
        var resolve = function(value){
            if (value instanceof Promise) {
                  // 若是 value 是個 Promise,遞歸執行
                  //等 Promise 狀態返回後,從新運行 resolve 或 reject
                  return value.then(resolve, reject)
            }
            stateF(RESOLVED, value)
        }
        
        var reject = function (reason) {
            stateF(REJECTED, reason)
          }
        
        // new Promise(() => throw Error('error))
          try {
            fn(resolve, reject);
          } catch (e) {
            reject(e);
          }
    }
    
    /*
     Promise.all方法用於將多個 Promise 實例,包裝成一個新的 Promise 實例。
     const p = Promise.all([p1, p2, p3]);
     上面代碼中,Promise.all方法接受一個數組做爲參數,p一、p二、p3都是 Promise 實例,若是不是,就會先調用Promise.resolve方法,將參數轉爲 Promise 實例,再進一步處理。
     只有p一、p二、p3的狀態都變成fulfilled,p的狀態纔會變成fulfilled,此時p一、p二、p3的返回值組成一個數組,傳遞給p的回調函數。
     只要p一、p二、p3之中有一個被rejected,p的狀態就變成rejected,此時第一個被reject的實例的返回值,會傳遞給p的回調函數。
     Promise.all方法的參數能夠不是數組,但必須具備 Iterator 接口,且返回的每一個成員都是 Promise 實例。
     * */
    Promise.all = function(arr){
        arr = Array.prototype.slice.apply(arr)
        var values = [], count = 0;
        var p = new Promise(function(resolve, reject){ 
            arr.forEach(function(item, index){
                Promise.resolve(item).then(function(value){
                    values[ index ] = value;
                    count++
                    if(p.currentState === PENDING && count === arr.length){
                        resolve(values)
                    }
                }).catch(function(e){
                    p.currentState === PENDING && reject(e)
                })
            })
        })
        return p
    }
    
    /*
     Promise.race方法一樣是將多個 Promise 實例,包裝成一個新的 Promise 實例。
     const p = Promise.race([p1, p2, p3]);
      只要p一、p二、p3之中有一個實例率先改變狀態,p的狀態就跟着改變。那個率先改變的 Promise 實例的返回值,就傳遞給p的回調函數。
     * */
    Promise.race = function(arr){
        arr = Array.prototype.slice.apply(arr)
        var p = new Promise(function(resolve, reject){ 
            arr.forEach(function(item, index){
                Promise.resolve(item).then(function(value){
                    p.currentState === PENDING && resolve(value)
                }).catch(function(e){
                    p.currentState === PENDING && reject(e)
                })
            })
        })
        return p
    }
    
    /*
     將現有對象轉爲 Promise 對象
     若是參數是 Promise 實例,那麼Promise.resolve將不作任何修改、原封不動地返回這個實例。
     參數是一個具備then方法的對象,Promise.resolve方法會將這個對象轉爲 Promise 對象,而後就當即執行thenable對象的then方法。
     若是參數是一個原始值,或者是一個不具備then方法的對象,則Promise.resolve方法返回一個新的 Promise 對象,狀態爲resolved。
     Promise.resolve方法容許調用時不帶參數,直接返回一個resolved狀態的 Promise 對象。
     * */
    Promise.resolve = function(value){
        if (value instanceof Promise) {
              return value
        }
        
        var fn = value.toString() === "[object Object]" && typeof value.then === 'function'
            ? value.then
            : function(resolve){ resolve(value) };
            
        return new Promise(fn)
    }
    
    /*
     Promise.reject(reason)方法也會返回一個新的 Promise 實例,該實例的狀態爲rejected。
     Promise.reject()方法的參數,會原封不動地做爲reject的參數,變成後續方法的參數。這一點與Promise.resolve方法不一致
     * */
    Promise.reject = function(value){
        return new Promise(function(resolve, reject){ reject(value) })
    }
    
    //提取的公共方法,then,catch,finally共用
    function bindF(state, fn){
        if(typeof fn === 'function'){
            if(this.currentState === PENDING){
                this.Callbacks.push({
                    state: state,
                    fn: fn
                })
            }else{
                //狀態改變後,不是REJECTED,直接運行
                state !== REJECTED && fn(this.value)
            }
        }
        return this
    }
    
    /*
     then方法的做用是爲 Promise 實例添加狀態改變時的回調函數。
     then方法的第一個參數是resolved狀態的回調函數,第二個參數(可選)是rejected狀態的回調函數。
     * */
    Promise.prototype.then = function(resolve, reject){
        var rs, rj;
        var p = new Promise(function(_resolve, _reject){
            rs = function(value){
                _resolve( typeof resolve === 'function' ? resolve(value) : value )
            }
            rj = function(value){
                _reject( typeof reject === 'function' ? reject(value) : value )
            }
        })
        
        bindF.call(this, RESOLVED, rs)
        bindF.call(this, REJECTED, rj)
        
        return p
    }
    
    /*
     Promise.prototype.catch方法是.then(null, rejection)或.then(undefined, rejection)的別名
     用於指定發生錯誤時的回調函數。
     * */
    Promise.prototype.catch = function(reject){
        return bindF.call(this, REJECTED, reject)
    }
    
    /*
     finally方法用於指定無論 Promise 對象狀態是 resolve 或 reject ,都會執行的操做。
     * */
    Promise.prototype.finally = function(fina){
        return bindF.call(this, FINALLY, fina)
    }
    
    return Promise
}()

if (typeof Promise.try !== 'function') {
    Promise.try = function(fn){
        return new Promise(function (resolve) {
            resolve(fn());
        });
    }
}

最後的 Promise.try

實際開發中,常常遇到一種狀況:不知道或者不想區分,函數f是同步函數仍是異步操做,可是想用 Promise 來處理它。
通常的,爲了確保同步函數同步執行,異步函數異步執行,咱們會這麼作:promise

const f1 = () => new Promise(resolve=>{
    setTimeout(_=>{
        resolve([1,2,3])
    },1000)
 });
 const f2 = _ => ({a: 1});
 new Promise( resolve => resolve(f1()) ).then(data=>{console.log(data)})
 new Promise( resolve => resolve(f2()) ).then(data=>{console.log(data)})
 //{a: 1}
 //[1, 2, 3]

同時,這麼寫還能夠更好的處理錯誤。緩存

若是同步或者異步方法自己是錯誤的,咱們需在外層套一層try來處理,這種寫法則能夠省略try,使代碼更美觀。app

針對這種寫法,如今已經有提案,提供Promise.try方法替代上面的寫法。frontend

使用案例:異步

Promise.try(_=>{
    myajax()
}).then(data=>{
    console.log(data)
})
相關文章
相關標籤/搜索