Promise簡單實現(正常思路版)

轉自: http://www.jianshu.com/p/473cd754311fjavascript

 

Promise

看了些promise的介紹,仍是感受不夠深刻,這個在解決異步問題上是一個很好的解決方案,因此詳細看一下,順便按照本身的思路實現一個簡單的Promise。html

Promise/A+規範:

  • 首先從新閱讀了下A+的規範:
    • promise表明了一個異步操做的最終結果,主要是經過then方法來註冊成功以及失敗的狀況,
    • Promise/A+歷史上說是實現了Promise/A的行爲而且考慮了一些不足之處,他並不關心如何建立,完成,拒絕Promise,只考慮提供一個可協做的then方法。

術語:

  • promise是一個擁有符合上面的特徵的then方法的對象或者方法。
  • thenable是定義了then方法的對象或者方法
  • value是任何合法的js的值(包括undefined,thenable或者promise)
  • exception是一個被throw申明拋出的值
  • reason是一個指明瞭爲何promise被拒絕

狀態要求:

  • promise必須是在pending,fulfilled或者rejected之間的一種狀態。
  • promise一旦從pending變成了fulfilled或則rejected,就不能再改變了。
  • promise變成fulfilled以後,必須有一個value,而且不能被改變
  • promise變成rejected以後,必須有一個reason,而且不能被改變

then方法的要求:

  • promise必須有個then方法來接觸當前的或者最後的value或者reason
  • then方法接受兩個參數,onFulfilled和onRejected,這兩個都是可選的,若是傳入的不是function的話,就會被忽略
  • 若是onFulfilled是一個函數,他必須在promise完成後被執行(不能提早),而且value是第一個參數,而且不能被執行超過一次
  • 若是onRejected是一個函數,他必須在promise拒絕後被執行(不能提早),而且reason是第一個參數,而且不能被執行超過一次
  • onFulfilled或者onRejected只能在執行上下文堆只包含了平臺代碼的時候執行(就是要求onfulfilled和 onrejected必須異步執行,必須在then方法被調用的那一輪事件循環以後的新執行棧執行,這裏可使用macro-task或者micro- task,這兩個的區別參見文章)
  • onFulfilled或者onRejected必須做爲function被執行(就是說沒有一個特殊的this,在嚴格模式中,this就是undefined,在粗糙的模式,就是global)
  • then方法可能在同一個promise被調用屢次,當promise被完成,全部的onFulfilled必須被順序執行,onRejected也同樣
  • then方法必須也返回一個promise(這個promise能夠是原來的promise,實現必須申明什麼狀況下二者能夠相等)promise2 = promise1.then(onFulfilled, onRejected);java

    • 若是onFulfilledonRejected都返回一個value x,執行2.3Promise的解決步驟[Resolve]
    • 若是onFulfilledonRejected都拋出exception e,promise2必須被rejected一樣的e
    • 若是onFulfilled不是個function,且promise1 is fulfilled,promise2也會fulfilled,和promise1的值同樣
    • 若是onRejected不是個function,且promise1 is rejected,promise2也會rejected,理由和promise1同樣

    這裏不論promise1被完成仍是被拒絕,promise2 都會被 resolve的,只有出現了一些異常纔會被rejectedgit

Promise的解決步驟==[Resolve]

  • 這個是將promise和一個值x做爲輸入的一個抽象操做。若是這個x是支持then的,他會嘗試讓promise接受x的狀態;不然,他會用x的值來fullfill這個promise。運行這樣一個東西,遵循如下的步驟
    • 若是promise和x指向同一個對象,則reject這個promise使用TypeError。
    • 若是x是一個promise,接受他的狀態
    • 若是x在pending,promise必須等待x的狀態改變
    • 若是x被fullfill,那麼fullfill這個promise使用同一個value
    • 若是x被reject,那麼reject這個promise使用同一個理由
    • 若是x是一個對象或者是個方法
    • 若是x.then返回了錯誤,則reject這個promise使用錯誤。
    • 若是then是一個方法,使用x爲this,resolvePromise爲一參,rejectPromise爲二參,
      • 若是resolvePromise被一個值y調用,那麼運行[Resolve]
      • 若是rejectPromise被reason r,使用r來reject這個promise
      • 若是resolvePromise和rejectPromise都被調用了,那麼第一個被調用的有優先權,其餘的beihulue
      • 若是調用then方法獲得了exception,若是上面的方法被調用了,則忽略,不然reject這個promise
    • 若是then方法不是function,那麼fullfill這個promise使用x
    • 若是x不是一個對象或者方法,那麼fullfill這個promise使用x

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

接下來正式寫一個promise

思路都是最正常的思路,想要寫一個Promise,確定得使用一個異步的函數,就拿setTimeout來作。數組

var p = new Promise(function(resolve){
    setTimeout(resolve, 100);
});
p.then(function(){console.log('success')},function(){console.log('fail')});

初步構建

上面是個最簡單的使用場景咱們須要慢慢來構建promise

function Promise(fn){
  //須要一個成功時的回調
  var doneCallback;
  //一個實例的方法,用來註冊異步事件
  this.then = function(done){
    doneCallback = done;
  }
  function resolve(){
    doneCallback();
  }
  fn(resolve);
}

加入鏈式支持

下面加入鏈式,成功回調的方法就得變成數組才能存儲異步

function Promise(fn){
  //須要成功以及成功時的回調
  var doneList = [];
  //一個實例的方法,用來註冊異步事件
  this.then = function(done ,fail){
    doneList.push(done);
    return this;
  }
  function resolve(){
    doneList.forEach(function(fulfill){
      fulfill();
    });
  }
  fn(resolve);
}

這裏promise裏面若是是同步的函數的話,doneList裏面仍是空的,因此能夠加個setTimeout來將這個放到js的最後執行。這裏主要是參照了promiseA+的規範,就像這樣函數

function resolve(){
  setTimeout(function(){
    doneList.forEach(function(fulfill){
      fulfill();
    });
  },0);
}

加入狀態機制

這時若是promise已經執行完了,咱們再給promise註冊then方法就怎麼都不會執行了,這個不符合預期,因此纔會加入狀態這種東西。更新過的代碼以下學習

function Promise(fn){
  //須要成功以及成功時的回調
  var state = 'pending';
  var doneList = [];
  //一個實例的方法,用來註冊異步事件
  this.then = function(done){
    switch(state){
      case "pending":
        doneList.push(done);
        return this;
        break;
      case 'fulfilled':
        done();
        return this;
        break;
    }
  }
  function resolve(){
    state = "fulfilled";
    setTimeout(function(){
      doneList.forEach(function(fulfill){
        fulfill();
      });
    },0);
  }
  fn(resolve);
}

加上異步結果的傳遞

如今的寫法根本沒有考慮異步返回的結果的傳遞,咱們來加上結果的傳遞

function resolve(newValue){
  state = "fulfilled";
  var value = newValue;
  setTimeout(function(){
    doneList.forEach(function(fulfill){
      value = fulfill(value);
    });
  },0);
}

支持串行

這樣子咱們就能夠將then每次的結果交給後面的then了。可是咱們的promise如今還不支持promise的串行寫法。好比咱們想要

var p = new Promise(function(resolve){
    setTimeout(function(){
      resolve(12);
    }, 100);
});
var p2 = new Promise(function(resolve){
    setTimeout(function(){
      resolve(42);
    }, 100);
});
p.then(
      function(name){
        console.log(name);return 33;
      }
  )
  .then(function(id){console.log(id)})
  .then(p2)
  .then(function(home){console.log(home)});

因此咱們必須改下then方法。

當then方法傳入通常的函數的時候,咱們目前的作法是將它推動了一個數組,而後return this來進行鏈式的調用,而且指望在resolve方法調用時執行這個數組。

最開始我是研究的美團工程師的一篇博客,到這裏的時候發現他的解決方案比較跳躍,因而我就按照普通的正常思路先嚐試了下:

若是傳入一個promise的話,咱們先嚐試繼續推入數組中,在resolve的地方進行區分,發現是可行的,我先貼下示例代碼,而後會有詳細的註釋。

function Promise(fn){
  //須要成功以及成功時的回調
  var state = 'pending';
  var doneList = [];
  this.then = function(done){
    switch(state){
      case "pending":
        doneList.push(done);
        return this;
        break;
      case 'fulfilled':
        done();
        return this;
        break;
    }
  }
  function resolve(newValue){
    state = "fulfilled";
    setTimeout(function(){
      var value = newValue;
      //執行resolve時,咱們會嘗試將doneList數組中的值都執行一遍
      //當遇到正常的回調函數的時候,就執行回調函數
      //當遇到一個新的promise的時候,就將原doneList數組裏的回調函數推入新的promise的doneList,以達到循環的目的
      for (var i = 0;i<doneList.length;i++){
        var temp = doneList[i](value)
        if(temp instanceof Promise){
            var newP =  temp;
            for(i++;i<doneList.length;i++){
                newP.then(doneList[i]);
            }
        }else{
            value = temp;
        }
      }
    },0);
  }
  fn(resolve);
}
var p = function (){
    return new Promise(function(resolve){
        setTimeout(function(){
          resolve('p 的結果');
        }, 100);
    });
}
var p2 = function (input){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log('p2拿到前面傳入的值:' + input)
            resolve('p2的結果');
        }, 100);
    });
}
p()
.then(function(res){console.log('p的結果:' + res); return 'p then方法第一次返回'})
.then(function(res){console.log('p第一次then方法的返回:'+res); return 'p then方法第二次返回'})
.then(p2)
.then(function(res){console.log('p2的結果:' + res)});

加入reject

我按照正常思路這麼寫的時候發現出了點問題,由於按照最上面的規範。即便一個promise被rejected,他註冊的then方法以後再註冊的 then方法會可能繼續執行resolve的。即咱們在then方法中爲了鏈式返回的this的status是可能會被改變的,假設咱們在實現中來改變狀 態而不暴露出來(這其實一點也不推薦)。

我直接貼實現的代碼,還有註釋做爲講解

function Promise(fn){
  var state = 'pending';
  var doneList = [];
  var failList= [];
  this.then = function(done ,fail){
    switch(state){
      case "pending":
        doneList.push(done);
        //每次若是沒有推入fail方法,我也會推入一個null來佔位
        failList.push(fail || null);
        return this;
        break;
      case 'fulfilled':
        done();
        return this;
        break;
      case 'rejected':
        fail();
        return this;
        break;
    }
  }
  function resolve(newValue){
    state = "fulfilled";
    setTimeout(function(){
      var value = newValue;
      for (var i = 0;i<doneList.length;i++){
        var temp = doneList[i](value);
        if(temp instanceof Promise){
            var newP =  temp;
            for(i++;i<doneList.length;i++){
                newP.then(doneList[i],failList[i]);
            }
        }else{
            value = temp;
        }
      }
    },0);
  }
  function reject(newValue){
    state = "rejected";
    setTimeout(function(){
      var value = newValue;
      var tempRe = failList[0](value);
      //若是reject裏面傳入了一個promise,那麼執行完這次的fail以後,將剩餘的done和fail傳入新的promise中
      if(tempRe instanceof Promise){
        var newP = tempRe;
        for(i=1;i<doneList.length;i++){
            newP.then(doneList[i],failList[i]);
        }
      }else{
        //若是不是promise,執行完當前的fail以後,繼續執行doneList
        value =  tempRe;
        doneList.shift();
        failList.shift();
        resolve(value);
      }
    },0);
  }
  fn(resolve,reject);
}
var p = function (){
    return new Promise(function(resolve,reject){
        setTimeout(function(){
          reject('p 的結果');
        }, 100);
    });
}
var p2 = function (input){
    return new Promise(function(resolve){
        setTimeout(function(){
            console.log('p2拿到前面傳入的值:' + input)
            resolve('p2的結果');
        }, 100);
    });
}
p()
.then(function(res){console.log('p的結果:' + res); return 'p then方法第一次返回'},function(value){console.log(value);return 'p then方法第一次錯誤的返回'})
.then(function(res){console.log('p第一次then方法的返回:'+res); return 'p then方法第二次返回'})
.then(p2)
.then(function(res){console.log('p2的結果:' + res)});

 

這篇文章是本身根據比較正常的思路來寫的一個簡單的promise。

接下來還會有篇文章來自習研究下美團那篇博客以及一些主流的promise的寫法,敬請期待。

參考:

先寫到這裏,順便給個github的傳送門,喜歡的朋友star一下啊,本身平時遇到的問題以及一下學習的經歷及寫代碼的思考都會在github上進行記錄~

相關文章
相關標籤/搜索