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
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函式的返回值。