實現一個完美符合Promise/A+規範的Promise

原文在個人博客中:原文地址 若是文章對您有幫助,您的star是對我最好的鼓勵~git

簡要介紹:Promise容許咱們經過鏈式調用的方式來解決「回調地獄」的問題,特別是在異步過程當中,經過Promise能夠保證代碼的整潔性和可讀性。本文主要解讀Promise/A+規範,並在此規範的基礎上,本身實現一個Promise.github

1、Promise的使用

在瞭解Promise規範以前,咱們知道主流的高版本瀏覽器已經支持ECMA中的Promise.npm

建立一個promise實例:數組

var p=new Promise(function(resolve,reject){
    setTimeout(function(){
       resolve("success")
    },1000);
    console.log("建立一個新的promise");
})
p.then(function(x){
  console.log(x)
})

//輸出:
建立一個新的promise
success
複製代碼

上述是一個promise的實例,輸出內容爲,「建立一個promise」,延遲1000ms後,輸出"success"。promise

從上述的例子能夠看出,promise方便處理異步操做。此外promise還能夠鏈式的調用:瀏覽器

var p=new Promise(function(resolve,reject){resolve()});
p.then(...).then(...).then(...)
複製代碼

此外Promise除了then方法外,還提供了Promise.resolve、Promise.all、Promise.race等等方法。bash

2、Promise/A+規範

Promise/A+規範擴展了早期的Promise/A proposal提案,咱們來解讀一下Promise/A+規範。異步

1.術語

(1)"promise"是一個對象或者函數,該對象或者函數有一個then方法async

(2)"thenable"是一個對象或者函數,用來定義then方法函數

(3)"value"是promise狀態成功時的值

(4)"reason"是promise狀態失敗時的值

咱們明確術語的目的,是爲了在本身實現promise時,保持代碼的規範性(也能夠跳過此小節)

2.要求

(1)一個promise必須有3個狀態,pending,fulfilled(resolved),rejected當處於pending狀態的時候,能夠轉移到fulfilled(resolved)或者rejected狀態。當處於fulfilled(resolved)狀態或者rejected狀態的時候,就不可變。

promise英文譯爲承諾,也就是說promise的狀態一旦發生改變,就永遠是不可逆的。

(2)一個promise必須有一個then方法,then方法接受兩個參數:

promise.then(onFulfilled,onRejected)
複製代碼

其中onFulfilled方法表示狀態從pending——>fulfilled(resolved)時所執行的方法,而onRejected表示狀態從pending——>rejected所執行的方法。

(3)爲了實現鏈式調用,then方法必須返回一個promise

promise2=promise1.then(onFulfilled,onRejected)
複製代碼

3、實現一個符合Promise/A+規範的Promise

解讀了Promise/A+規範以後,下面咱們來看如何實現一個Promise, 首先構造一個myPromise函數,關於全部變量和函數名,應該與規範中保持相同。

1.v1.0 初始版本myPromise

function myPromise(constructor){
    let self=this;
    self.status="pending" //定義狀態改變前的初始狀態
    self.value=undefined;//定義狀態爲resolved的時候的狀態
    self.reason=undefined;//定義狀態爲rejected的時候的狀態
    function resolve(value){
        //兩個==="pending",保證了狀態的改變是不可逆的
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
       }
    }
    function reject(reason){
        //兩個==="pending",保證了狀態的改變是不可逆的
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
       }
    }
    //捕獲構造異常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}
複製代碼

同時,須要在myPromise的原型上定義鏈式調用的then方法:

myPromise.prototype.then=function(onFullfilled,onRejected){
   let self=this;
   switch(self.status){
      case "resolved":
        onFullfilled(self.value);
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
   }
}
複製代碼

上述就是一個初始版本的myPromise,在myPromise裏發生狀態改變,而後在相應的then方法裏面根據不一樣的狀態能夠執行不一樣的操做。

var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//輸出1
複製代碼

可是這裏myPromise沒法處理異步的resolve.好比:

var p=new myPromise(function(resolve,reject){setTimeout(function(){resolve(1)},1000)});

p.then(function(x){console.log(x)})
//無輸出
複製代碼

2.v2.0基於觀察模式實現

爲了處理異步resolve,咱們修改myPromise的定義,用2個數組onFullfilledArray和onRejectedArray來保存異步的方法。在狀態發生改變時,一次遍歷執行數組中的方法。

function myPromise(constructor){
    let self=this;
    self.status="pending" //定義狀態改變前的初始狀態
    self.value=undefined;//定義狀態爲resolved的時候的狀態
    self.reason=undefined;//定義狀態爲rejected的時候的狀態
    self.onFullfilledArray=[];
    self.onRejectedArray=[];
    function resolve(value){
       if(self.status==="pending"){
          self.value=value;
          self.status="resolved";
          self.onFullfilledArray.forEach(function(f){
                f(self.value);
                //若是狀態從pending變爲resolved,
                //那麼就遍歷執行裏面的異步方法
          });
        
       }
    }
    function reject(reason){
       if(self.status==="pending"){
          self.reason=reason;
          self.status="rejected";
          self.onRejectedArray.forEach(function(f){
              f(self.reason);
             //若是狀態從pending變爲rejected, 
             //那麼就遍歷執行裏面的異步方法
          })
       }
    }
    //捕獲構造異常
    try{
       constructor(resolve,reject);
    }catch(e){
       reject(e);
    }
}
複製代碼

對於then方法,狀態爲pending時,往數組裏面添加方法:

myPromise.prototype.then=function(onFullfilled,onRejected){
   let self=this;
   switch(self.status){
      case "pending":
        self.onFullfilledArray.push(function(){
             onFullfilled(self.value)
        });
        self.onRejectedArray.push(function(){
             onRejected(self.reason)
        });
      case "resolved":
        onFullfilled(self.value);
        break;
      case "rejected":
        onRejected(self.reason);
        break;
      default:       
   }
}
複製代碼

這樣,經過兩個數組,在狀態發生改變以後再開始執行,這樣能夠處理異步resolve沒法調用的問題。這個版本的myPromise就能處理全部的異步,那麼這樣作就完整了嗎?

沒有,咱們作Promise/A+規範的最大的特色就是鏈式調用,也就是說then方法返回的應該是一個promise。

3.v3.0then方法實現鏈式調用

要經過then方法實現鏈式調用,那麼也就是說then方法每次調用須要返回一個primise,同時在返回promise的構造體裏面,增長錯誤處理部分,咱們來改造then方法

myPromise.prototype.then=function(onFullfilled,onRejected){
    let self=this;
    let promise2;
    switch(self.status){
      case "pending":
        promise2=new myPromise(function(resolve,reject){
             self.onFullfilledArray.push(function(){
                try{
                   let temple=onFullfilled(self.value);
                   resolve(temple)
                }catch(e){
                   reject(e) //error catch
                }
             });
             self.onRejectedArray.push(function(){
                 try{
                   let temple=onRejected(self.reason);
                   reject(temple)
                 }catch(e){
                   reject(e)// error catch
                 }
             });
        })
      case "resolved":
        promise2=new myPromise(function(resolve,reject){
            try{
              let temple=onFullfilled(self.value);
              //將上次一then裏面的方法傳遞進下一個Promise的狀態
              resolve(temple);
            }catch(e){
              reject(e);//error catch
            }
        })
        break;
      case "rejected":
        promise2=new myPromise(function(resolve,reject){
            try{
               let temple=onRejected(self.reason);
               //將then裏面的方法傳遞到下一個Promise的狀態裏
               resolve(temple);   
            }catch(e){
               reject(e);
            }
        })
        break;
      default:       
   }
   return promise2;
}
複製代碼

這樣經過then方法返回一個promise就能夠實現鏈式的調用:

p.then(function(x){console.log(x)}).then(function(){console.log("鏈式調用1")}).then(function(){console.log("鏈式調用2")})
//輸出
1
鏈式調用1
鏈式調用2
複製代碼

這樣咱們雖然實現了then函數的鏈式調用,可是還有一個問題,就是在Promise/A+規範中then函數裏面的onFullfilled方法和onRejected方法的返回值能夠是對象,函數,甚至是另外一個promise。

4.v4.0 then函數中的onFullfilled和onRejected方法的返回值問題

特別的爲了解決onFullfilled和onRejected方法的返回值多是一個promise的問題。

(1)首先來看promise中對於onFullfilled函數的返回值的要求

I)若是onFullfilled函數返回的是該promise自己,那麼會拋出類型錯誤

II)若是onFullfilled函數返回的是一個不一樣的promise,那麼執行該promise的then函數,在then函數裏將這個promise的狀態轉移給新的promise III)若是返回的是一個嵌套類型的promsie,那麼須要遞歸。

IV)若是返回的是非promsie的對象或者函數,那麼會選擇直接將該對象或者函數,給新的promise。

根據上述返回值的要求,咱們要從新的定義resolve函數,這裏Promise/A+規範裏面稱爲:resolvePromise函數,該函數接受當前的promise、onFullfilled函數或者onRejected函數的返回值、resolve和reject做爲參數。

下面咱們來看resolvePromise函數的定義:

function resolvePromise(promise,x,resolve,reject){
  if(promise===x){
     throw new TypeError("type error")
  }
  let isUsed;
  if(x!==null&&(typeof x==="object"||typeof x==="function")){
      try{
        let then=x.then;
        if(typeof then==="function"){
           //是一個promise的狀況
           then.call(x,function(y){
              if(isUsed)return;
              isUsed=true;
              resolvePromise(promise,y,resolve,reject);
           },function(e){
              if(isUsed)return;
              isUsed=true;
              reject(e);
           })
        }else{
           //僅僅是一個函數或者是對象
           resolve(x)
        }
      }catch(e){
         if(isUsed)return;
         isUsed=true;
         reject(e);
      }
  }else{
    //返回的基本類型,直接resolve
    resolve(x)
  }
}
複製代碼

改變了resolvePromise函數以後,咱們在then方法裏面的調用也變成了resolvePromise而不是promise。

myPromise.prototype.then=function(onFullfilled,onRejected){
    let self=this;
    let promise2;
    switch(self.status){
      case "pending":
        promise2=new myPromise(function(resolve,reject){
             self.onFullfilledArray.push(function(){
                setTimeout(function(){
                  try{
	                   let temple=onFullfilled(self.value);
	                   resolvePromise(temple)
	                }catch(e){
	                   reject(e) //error catch
	                }
                })
             });
             self.onRejectedArray.push(function(){
                setTimeout(function(){
                   try{
	                   let temple=onRejected(self.reason);
	                   resolvePromise(temple)
	                 }catch(e){
	                   reject(e)// error catch
	               }
                })
             });
        })
      case "resolved":
        promise2=new myPromise(function(resolve,reject){
           setTimeout(function(){
               try{
	              let temple=onFullfilled(self.value);
	              //將上次一then裏面的方法傳遞進下一個Promise狀態
	              resolvePromise(temple);
	            }catch(e){
                  reject(e);//error catch
               }
           })
        })
        break;
      case "rejected":
        promise2=new myPromise(function(resolve,reject){
           setTimeout(function(){
             try{
               let temple=onRejected(self.reason);
               //將then裏面的方法傳遞到下一個Promise的狀態裏
               resolvePromise(temple);   
             }catch(e){
               reject(e);
             }
           })
        })
        break;
      default:       
   }
   return promise2;
}
複製代碼

這樣就能處理onFullfilled各類返回值的狀況。

var p=new Promise(function(resolve,reject){resolve("初始化promise")})
p.then(function(){return new Promise(function(resolve,reject){resolve("then裏面的promise返回值")})}).then(function(x){console.log(x)})

//輸出
then裏面promise的返回值
複製代碼

到這裏可能有點亂,咱們再理一理,首先返回值有兩個:

  • then函數的返回值——>返回一個新promise,從而實現鏈式調用

  • then函數中的onFullfilled和onRejected方法——>返回基本值或者新的promise

這二者實際上是有關聯的,onFullfilled方法的返回值能夠決定then函數的返回值。

4、檢測是否徹底符合Promise/A+規範

npm install -g promises-aplus-tests 
複製代碼

具體用法請看promise test而後

promises-aplus-tests  myPromise.js
複製代碼

結果爲:

這裏寫圖片描述
說明咱們的實現徹底符合Promise/A+規範。

完整代碼的地址

https://github.com/forthealllight/promise-achieve

5、最後補充Typescript實現的Promise/A+規範(能夠忽略此節)

interface IConstructor{
  (resolve:IResolve,reject:IReject):void
}
interface IResolve {
    (x:any):void
}
interface IReject {
    (x:any):void
}
function myPromise(constructor:IConstructor):void{
  let self:object=this;
  self.status="pending";
  self.value=undefined;//if pending->resolved
  self.reason=undefined;//if pending->rejected
  self.onFullfilledArray=[];//to deal with async(resolved)
  self.onRejectedArray=[];//to deal with async(rejeced)
  let resolve:IResolve;
  resolve=function(value:any):void{
    //pending->resolved
    if(self.status==="pending"){
      self.status="resolved";
      self.value=value;
      self.onFullfilledArray.forEach(function(f){
        f(self.value);
      })
    }
  }
  let reject:IReject;
  reject=function(reason:any):void{
    if(self.status==="pending"){
      self.status="rejected";
      self.reason=reason;
      self.onRejectedArray.forEach(function(f){
        f(self.reason);
      })
    }
  }
  //According to the definition that the function "constructor" accept two parameters
  //error catch
  try {
    constructor(resolve,reject);
  } catch (e) {
    reject(e);
  }
}
複製代碼

單純的寫個工具函數,用ts仍是有點影響可讀性。

相關文章
相關標籤/搜索