從上次更新Promise/A+規範後,已經好久沒有更新博客了。以前因爲業務須要,完成了一個TypeScript語言的Promise庫。此次咱們來和你們一步一步介紹下,咱們如何實現一個符合Promise/A+規範的Promise庫。前端
若是對Promise/A+規範還不太瞭解的同窗,建議先看看上一篇博客——前端基礎知識儲備——Promise/A+規範。git
首先,咱們來看下,在我實現的這一個Promise中,代碼由下面這幾部分組成:github
經過上面這四個部分,咱們就可以獲得一個完整的Promise。這四個部分互相有關聯,接下來咱們一個一個模塊來看。typescript
在以前的Promiz的源碼分析的博客中我有提到過,咱們如何來實現一個異步函數執行器。經過JavaScript的執行原理咱們能夠知道,若是要實現異步執行相關函數的話,咱們能夠選擇使用宏任務和微任務,這一點在Promise/A+的規範中也有說起。所以,下面咱們提供了一個用宏任務來實現異步函數執行器的代碼供你們參考。segmentfault
let index = 0; if (global.postMessage) { global.addEventListener('message', (e) => { if (e.source === global) { let id = e.data; if (isRunningTask) { nextTick(functionStorage[id]); } else { isRunningTask = true; try { functionStorage[id](); } catch (e) { } isRunningTask = false; } delete functionStorage[id]; functionStorage[id] = void 0; } }); } function nextTick(func) { if (global.setImmediate) { global.setImmediate(func); } else if (global.postMessage) { functionStorage[++index] = func; global.postMessage(index, '*') } else { setTimeout(func); } }
經過上面的代碼咱們能夠看到,咱們一共使用了setImmediate
、postMessage
、setTimeout
這三個添加宏任務的方法來進行一步函數執行。數組
說完了如何進行異步函數的執行,咱們來看下相關的常量與屬性。在實現Promise以前,咱們須要定義一些常量和類屬性,用於後面存儲數據。讓咱們一個一個來看下。promise
首先,Promise共有5個狀態,咱們須要用常量來進行定義,具體以下:異步
enum State { pending = 0, resolving = 1, rejecting = 2, resolved = 3, rejected = 4 };
這五個常量分別對應Promise中的5個狀態,相信你們可以從名字中理解,咱們就很少講了。函數
在Promise中,咱們須要一些屬性來存儲數據狀態和後續的Promise引用,具體以下:工具
class Promise { private _value; private _reason; private _next = []; public state: State = 0; public fn; public er; }
咱們對屬性進行逐一說明:
_value
,表示在resolved狀態時,用來存儲當前的值。_reason
,表示在rejected狀態時,用來存儲當前的緣由。_next
,表示當前Promise後面跟着then
函數的引用。fn
,表示當前Promise中的then
方法的第一個回調函數。er
,表示當前Promise中的then
方法的的第二個回調函數(即catch
的第一個參數,下面看catch
實現方法就能理解)。看完了常量與類的屬性,咱們來看下類的靜態方法。
首先,若是咱們要實現一個Promise,咱們須要一個構造函數來初始化最初的Promise。具體代碼以下:
class Promise { constructor(resolver?) { if (typeof resolver !== 'function' && resolver !== undefined) { throw TypeError() } if (typeof this !== 'object') { throw TypeError() } try { if (typeof resolver === 'function') { resolver(this.resolve.bind(this), this.reject.bind(this)); } } catch (e) { this.reject(e); } } }
從Promise/A+的規範來看,咱們能夠知道,若是resolver
存在而且不是一個function的話,那麼咱們就應該拋出一個錯誤;不然,咱們應該將resolve
和reject
方法傳給resolver
做爲參數。
那麼,resolve
和reject
方法又是作什麼的呢?這兩個方法主要是用來讓當前的這個Promise轉換狀態的,即從pending
狀態轉換爲resolving
或者rejecting
狀態。下面讓咱們來具體看下代碼:
class Promise { resolve(value) { if (this.state === State.pending) { this._value = value; this.state = State.resolving; nextTick(this._handleNextTick.bind(this)); } return this; } reject(reason) { if (this.state === State.pending) { this._reason = reason; this.state = State.rejecting; this._value = void 0; nextTick(this._handleNextTick.bind(this)); } return this; } }
從上面的代碼中咱們能夠看到,當resolve
或者reject
方法被觸發時,咱們都改變了當前這個Proimse的狀態,而且異步調用執行了_handleNextTick
方法。狀態的改變標誌着當前的Promise已經從pending
狀態改變成了resolving
或者rejecting
狀態,而相應_value
和_reson
也表示上一個Promise傳遞給下一個Promise的數據。
那麼,這個_handleNextTick
方法又是作什麼的呢?其實,這個方法的做用很簡單,就是用來處理當前這個Promise後面跟着的then
函數傳遞進來的回調函數fn
和er
。
在瞭解_handleNextTick
以前,咱們們先看下then
函數和catch
函數的實現。
class Promise { public then(fn, er?) { let promise = new Promise(); promise.fn = fn; promise.er = er; if (this.state === State.resolved) { promise.resolve(this._value); } else if (this.state === State.rejected) { promise.reject(this._reason); } else { this._next.push(promise); } return promise; } public catch(er) { return this.then(null, er); } }
由於catch
函數調用就是一個then
函數的別名,咱們下面就只討論then
函數。
在then
函數執行時,咱們會建立一個新的Promise,而後將傳入的兩個回調函數用新的Promise的屬性保存下來。而後,先判斷當前的Promise的狀態,若是已是resolved
或者rejected
狀態時,咱們當即調用新的Promise中resolve
或者reject
方法,讓下將當前Promise的_value
或者_reason
傳遞給下一個Promise,而且觸發下一個Promise的狀態改變。若是當前Promise的狀態仍然爲pending
時,那麼就將這個新生成的Promise保存下來,等當前這個Promise的狀態改變後,再觸發新的Promise變化。最後,咱們返回了這個Promise的實例。
看完了then
函數,咱們就能夠來看下咱們提到過的handleNextTick
函數。
class Promise { private _handleNextTick() { try { if (this.state === State.resolving && typeof this.fn === 'function') { this._value = this.fn.call(getThis(), this._value); } else if (this.state === State.rejecting && typeof this.er === 'function') { this._value = this.er.call(getThis(), this._reason); this.state = 1; } } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; this._finishThisTypeScriptPromise(); } // if promise === x, use TypeError to reject promise // 若是promise和x指向同一個對象,那麼用TypeError做爲緣由拒絕promise if (this._value === this) { this.state = State.rejecting; this._reason = new TypeError(); this._value = void 0; } this._finishThisTypeScriptPromise(); } }
咱們先來看一個簡單版的_handleNextTick
函數,這樣可以幫助咱們快速理解Promise主流程。
異步觸發了_handleNextTick
函數後,咱們會判斷當前用戶處於的狀態,若是當前Promise是resolving
狀態,咱們就會調用fn
函數,即咱們在then
函數調用時給新的Promise設置的那個fn
函數;而如過當前Promise是rejecting
狀態,咱們就會調用er
函數。
上面提到的getThis
方法是用來獲取特定的this值,具體的規範要求咱們將在稍後再進行介紹。
經過執行這兩個同步的fn
或er
函數,咱們可以獲得當前Promise執行完傳入回調後的值。在這裏須要說明的是:咱們在執行fn
或者er
函數以前,咱們在_value
和_reason
中存放的值,是上一個Promise傳遞下來的值。只有當執行完了fn
或者er
函數後,_value
和_reason
中存放的值纔是咱們須要傳遞給下一個Promise的值。
你們到這裏可能會奇怪,咱們的this
指向沒有發生變化,可是爲何咱們的this
指向的是那個新的Promise,而不是原來的那個Promise呢?
咱們能夠從另一個角度來看待這個問題:咱們當前的這個Promise是否是由上一個Promise所產生的呢?若是是這種狀況的話,咱們就能夠理解,在上一個Promise產生當前Promise的時候,就設置了fn
和er
兩個函數。
你們可能又會問,那麼咱們第一個Promise的fn
和er
這兩個參數是怎麼來的呢?
那麼咱們就須要仔細看下上面這個邏輯了。下面咱們只討論第一個Promise處於pending
的狀況,其他的狀況與這種情形基本相同。第一個Promise由於沒有上一個Promise去設置fn
和er
兩個參數,所以這兩個參數的值就是undefined
。因此在上面的邏輯中,咱們已經排除了這種狀況,直接進入了_finishThisTypeScriptPromise
函數中。
咱們在這裏須要特別說明下的是,有些人會認爲咱們在調用then
函數傳入的兩個回調函數fn
和er
時,當前Promise就結束了,其實並非這樣,咱們是獲得了fn
或者er
兩個函數的返回值,再將值傳遞給下一個Promise時,上一個Promise纔會結束。關於這個邏輯咱們能夠看下_finishThisTypeScriptPromise
函數。
_finishThisTypeScriptPromise
函數的代碼以下:
class Promise { private _finishThisTypeScriptPromise() { if (this.state === State.resolving) { this.state = State.resolved; this._next.map((nextTypeScriptPromise) => { nextTypeScriptPromise.resolve(this._value); }); } else { this.state = State.rejected; this._next.map((nextTypeScriptPromise) => { nextTypeScriptPromise.reject(this._reason); }); } } }
從_finishThisTypeScriptPromise
函數中咱們能夠看到,咱們在獲得了須要傳遞給下一個Promise的_value
或者_reason
後,利用map
方法逐個調用咱們保存的新生成的Promise實例,調用它的resolve
方法,所以咱們又觸發了這個Promise的狀態從pending
轉變爲resolving
或者rejecting
。
到這裏咱們就已經徹底瞭解了一個Promise從最開始的建立,到最後結束的整個生命週期。下面咱們來看下在Promise/A+規範中提到的一些分支邏輯的處理狀況。
首先,讓咱們來了解下什麼是Thenable實例。Thenable實例指的是屬性中有then
函數的對象。Promise就是的一種特殊的Thenable對象。
下面,爲了方便講解,咱們將用Promise來代替Thenable進行講解,其餘的Thenable類你們能夠參考相似思路進行分析。
若是咱們在傳遞給咱們的_value
中是一個Promise實例,那麼咱們必須在等待傳入的Promise狀態轉換到resolved
以後,當前的Promise纔可以繼續往下執行,即咱們從傳入的Promise中獲得了一個非Thenable返回值時,咱們才能用這個值來調用屬性中的fn
或者er
方法。
那麼,咱們要怎麼樣才能獲取到傳入的這個Promise的返回值呢?在Promise中其實用到了一個很是巧妙的方法:由於傳入的Promise中有一個then
函數(Thenable定義),所以咱們就調用then
函數,在第一個回調函數fn
中傳入獲取_value
,觸發當前的Promise繼續執行。若是是觸發了第二個回調函數er
,那麼就用在er
中獲得的_reason
來拒絕掉當前的Promise。具體判斷邏輯以下:
class Promise { private _handleNextTick() { let ref; let count = 0; try { // 判斷傳入的this._value是否爲一個thanable // check if this._value a thenable ref = this._value && this._value.then; } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; return this._handleNextTick(); } if (this.state !== State.rejecting && (typeof this._value === 'object' || typeof this._value === 'function') && typeof ref === 'function') { // add a then function to get the status of the promise // 在原有TypeScriptPromise後增長一個then函數用來判斷原有promise的狀態 try { ref.call(this._value, (value) => { if (count++) { return; } this._value = value; this.state = State.resolving; this._handleNextTick(); }, (reason) => { if (count++) { return; } this._reason = reason; this.state = State.rejecting; this._value = void 0; this._handleNextTick(); }); } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; this._handleNextTick(); } } else { try { if (this.state === State.resolving && typeof this.fn === 'function') { this._value = this.fn.call(getThis(), this._value); } else if (this.state === State.rejecting && typeof this.er === 'function') { this._value = this.er.call(getThis(), this._reason); this.state = 1; } } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; this._finishThisTypeScriptPromise(); } this._finishThisTypeScriptPromise(); } } }
在Promise/A+規範中,若是返回的_value
值等於用戶自身時,須要用TypeError錯誤拒絕掉當前的Promise。所以咱們須要在_handleNextTick
中加入如下判斷代碼:
class Promise { private _handleNextTick() { let ref; let count = 0; try { // 判斷傳入的this._value是否爲一個thanable // check if this._value a thenable ref = this._value && this._value.then; } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; return this._handleNextTick(); } if (this.state !== State.rejecting && (typeof this._value === 'object' || typeof this._value === 'function') && typeof ref === 'function') { // add a then function to get the status of the promise // 在原有TypeScriptPromise後增長一個then函數用來判斷原有promise的狀態 ... } else { try { if (this.state === State.resolving && typeof this.fn === 'function') { this._value = this.fn.call(getThis(), this._value); } else if (this.state === State.rejecting && typeof this.er === 'function') { this._value = this.er.call(getThis(), this._reason); this.state = 1; } } catch (e) { this.state = State.rejecting; this._reason = e; this._value = void 0; this._finishThisTypeScriptPromise(); } // if promise === x, use TypeError to reject promise // 若是promise和x指向同一個對象,那麼用TypeError做爲緣由拒絕promise if (this._value === this) { this.state = State.rejecting; this._reason = new TypeError(); this._value = void 0; } this._finishThisTypeScriptPromise(); } } }
在Promise/A+規範中規定:咱們在調用fn
和er
兩個回調函數時,this的指向有限制。在嚴格模式下,this的值應該爲undefined
;在寬鬆模式下時,this的值應該爲global
。
所以,咱們還須要提供一個getThis
函數用於處理上述狀況。具體代碼以下:
class Promise { ... } function getThis() { return this; }
咱們經過上面說到的類方法和一些特定分支的邏輯處理,咱們就已經實現了一個符合基本功能的Promise類。那麼,下面咱們來看下ES6中提供的一些標準API咱們如何來進行實現。具體API以下:
下面咱們就一個一個方法來看下。
首先咱們來看下最簡單的resolve
和reject
方法。
class Promise { public static resolve(value?) { if (TypeScriptPromise._d !== 1) { throw TypeError(); } if (value instanceof TypeScriptPromise) { return value; } return new TypeScriptPromise((resolve) => { resolve(value); }); } public static reject(value?) { if (TypeScriptPromise._d !== 1) { throw TypeError(); } return new TypeScriptPromise((resolve, reject) => { reject(value); }); } }
經過上面代碼咱們能夠看到,resolve
和reject
方法基本上就是直接使用了內部的constructor
方法進行Promise構建。
class Promise { public static all(arr) { if (TypeScriptPromise._d !== 1) { throw TypeError(); } if (!(arr instanceof Array)) { return TypeScriptPromise.reject(new TypeError()); } let promise = new TypeScriptPromise(); function done() { // 統計還有多少未完成的TypeScriptPromise // count the unresolved promise let unresolvedNumber = arr.filter((element) => { return element && element.then; }).length; if (!unresolvedNumber) { promise.resolve(arr); } arr.map((element, index) => { if (element && element.then) { element.then((value) => { arr[index] = value; done(); return value; }); } }); } done(); return promise; } }
下面咱們根據上面的代碼來簡單說下all
函數的基本思路。
首先咱們須要先建立一個新的Promise用於返回,保證後面用戶調用then
函數進行後續邏輯處理時能夠設置新Promise的fn
和er
這兩個回調函數。
而後,咱們怎麼獲取上面Promise數組中每個Promise的值呢?方法很簡單,咱們在前面就已經介紹過:咱們調用了每個Promise的then
函數用來獲取當前這個Promise的值。而且,在每一個Promise完成時,咱們都檢查下是否全部的Promise都已經完成,若是已經完成,則觸發新Promise的狀態從pending
轉換爲resolving
或者rejecting
。
class Promise { public static race(arr) { if (TypeScriptPromise._d !== 1) { throw TypeError(); } if (!(arr instanceof Array)) { return TypeScriptPromise.reject(new TypeError()); } let promise = new TypeScriptPromise(); function done(value?) { if (value) { promise.resolve(value); } let unresolvedNumber = arr.filter((element) => { return element && element.then; }).length; if (!unresolvedNumber) { promise.resolve(arr); } arr.map((element, index) => { if (element && element.then) { element.then((value) => { arr[index] = value; done(value); return value; }); } }); } done(); return promise; } }
race
的思路與all
基本一致。只是咱們在處理函數上不一樣。當咱們只要檢測到數組中的Promise有一個已經轉換到了resolve
或者rejected
狀態(經過沒有then
函數來進行判斷)時,就會當即出發新建立的Promise示例的狀態從pending
轉換爲resolving
或者rejecting
。
咱們對Promise的異步函數執行器、常量與屬性、類方法、類靜態方法進行了逐一介紹,讓你們對整個Promise的構造和聲明週期有了一個深度的理解和認知。在整個開發中須要注意到的一些關鍵點和細節,我在上面也一一說明了。你們只須要按照這個思路,對照Promise/A+規範就可以完成一個符合規範的Promise庫。
最後,給你們提供一個Promise/A+測試工具,你們實現了本身的Promise後,可使用這個工具來測試是否徹底符合整個Promise/A+規範。固然,你們若是想使用個人現成代碼,也歡迎你們使用個人代碼Github/typescript-proimse。