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


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

 

Promise

看了些promise的介紹,還是感覺不夠深入,這個在解決異步問題上是一個很好的解決方案,所以詳細看一下,順便按照自己的思路實現一個簡單的Promise。

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

    • 如果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的,只有出現了一些異常才會被rejected

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。

接下來正式寫一個promise

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

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

初步構建

上面是個最簡單的使用場景我們需要慢慢來構建

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上進行記錄~

 


免責聲明!

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



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