1. 程式人生 > >promise實現原理

promise實現原理

簡要介紹:Promise允許我們通過鏈式呼叫的方式來解決“回撥地獄”的問題,特別是在非同步過程中,通過Promise可以保證程式碼的整潔性和可讀性。本文主要解讀Promise/A+規範,並在此規範的基礎上,自己實現一個Promise.

一、Promise的使用

在瞭解Promise規範之前,我們知道主流的高版本瀏覽器已經支援ECMA中的Promise.

建立一個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還可以鏈式的呼叫:

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

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

二、Promise/A+規範

Promise/A+規範擴充套件了早期的Promise/A proposal

提案,我們來解讀一下Promise/A+規範。

1.術語

(1)"promise"是一個物件或者函式,該物件或者函式有一個then方法

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

三、實現一個符合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函式的返回值。