最近老是聽到前端面試要求手寫promise,promise做爲解決前端回調地獄的方案被普遍運用在前端異步調用上,nodejs更是出了一版返回promise IO接口,因此不管從面試仍是實際工做中熟悉promise都是一個合格前端必備技能。javascript
咱們知道要實現一個功能,首先要了解需求是什麼,實現Promise也是如此。前端
那麼Promise到底提供了哪些功能?咱們先把Promise中經常使用的方法列出來java
// 使用Promise的標準開頭 new Promise(),可見Promise是一個類 class Promise { /** * 經常使用方式Promise.resolve(something).then(...) * 可見Promise.resolve返回一個新的Promise實例 **/ static resolve() {} // 同上 static reject() {} /** * 構造函數接收一個函數做爲入參 * 該函數會接收resolve,reject做爲入參,而且當即執行 * Promise將在fn調用resolve/reject後決議 */ constructor(fn) {} /** * 根據constructor咱們能夠推斷Promise有三個狀態分別是 * pending,fullfilled,rejected * 咱們須要一個內部變量來保存promise的當前狀態 */ _status = 'pending' // promise的初始狀態爲pending /** * 咱們須要一個變量保存promise的決議結果 * 考慮 new Promise((resolve) => resolve(2)); **/ _result /** * 考慮一下resolve作了什麼事情 * 其實resolve改變promise的狀態並將入參做爲回調函數的入參 **/ resolve(ret) {} // 同上 reject(ret) {} /** * then稱得上是promise的核心方法,到底then作了什麼,咱們考慮一下 * then會接收兩個函數,在promise的狀態發生改變後會調用對應的函數 * 因此在這裏then的做用應當是個事件註冊器。 * 須要注意的是then是能屢次調用的 * const promise = new Promise(fn) * promise.then(fn1) * promise.then(fn2) * 另外then是支持鏈式調用的,如promise.then(fn3).then(fn4) * 因此調用then還應當返回一個promise對象 **/ then(fn, fn2) {} // 描述完then後,咱們發現咱們須要兩個回調隊列來保存使用then註冊的回調 _successCallback = [] _errorCallback = [] // 咱們再定義一個內部方法來異步執行這些回調函數 // 使用入參type區分執行successCallback仍是errorCallback _runCallbackAsync(type) {} }
分析完Promise的核心功能後,讓咱們依次開始實現這些接口。node
// 先不看靜態方法和實例方法,從構造器開始實現 class Promise { ... constructor(fn) { // 根據以前描述構造器做用主要是運行傳入的fn // 將fn放在try catch中運行,防止fn執行出錯 try { //new Promise((resolve, reject) => {}) fn(this.resolve.bind(this), this.reject.bind(this)) } catch(e) { this.reject(e) } } // resolve調用以後改變後promise的狀態,而且異步執行callback resolve(result) { // promise的狀態不是pending,說明該promise已經調用過reject/resolve if(this._status !== 'pending') throw new Error('重複決議promise') // 保存決議結果 this._result = result // 異步執行callback this._runCallbackAsync('success') } // reject也是同理 reject(err) { if(this._status !== 'pending') throw new Error('重複決議promise') this._result = err // 這裏咱們執行錯誤回調 this._runCallbackAsync('error') } ... }
在寫promise.then以前咱們從新考慮下這個方法到底作了什麼?咱們逐步實現這個功能點。git
class Promise { ... // 省略先後代碼 then(fn1, fn2) { // 支持鏈式調用,當即回覆一個新的promise對象 return new Promise((resolve, reject) => { /** 而後咱們要將對應的回調函數推入事件隊列,他們將在本個promise * 決議後由_runCallbackAsync執行。 * 但執行完對應事件後須要將執行結果傳到下一個promise中 * 因此咱們須要對回調函數進行小小的處理 */ // 接收決議結果 const successCallBack = (result) => { try { // 將promise的決議結果傳遞給回調函數去執行,並把回調函數的結果做爲下一個promise的決議結果 resolve(fn1(result)) } catch(e) { // 若是執行出錯,應該將錯誤信息傳遞給promise reject(e) } } const errorCallback = (e) => { try { reject(fn2(e)) } catch(err) { reject(err) } } // 將回調函數推入事件隊列 if (fn1 && this._status !== 'error') this._successCallback.push(fn1) if (fn2 && this._status !== 'success') this._errorCallback.push(fn2) // 若是promise已經決議,則當即執行相應的回調 if(this._status === 'success') this._runCallbackAsync('success') if(this._status === 'error') this._runCallbackAsync('error') }) } // 實現 _runCallbackAsync(type) { let eventQueue; if(type === 'error') eventQueue = this._errorCallback; else eventQueue = this._successCallback; // 執行回調, 使用settimeout模擬異步執行 setTimeout(() => { eventQueue.forEach(callback => callback(this._result)); // 清空事件隊列,兩個事件隊列都須要刪除,由於promise決議後狀態不可變動,決議執行完應當清空全部隊列,以解除引用關係 this._errorCallback.length = 0; this._successCallback.length = 0; }, 0) } ... }
到此Promise的核心方法已經實現完畢,剩下的方法咱們很容易使用現有方法進行實現,例如:github
class Promise { ··· catch(fn) { return this.then(undefined, fn); } static resolve(any) { // 使用鴨子類型去判斷any的類型 if ( typeof any.then === 'function' typeof any.catch === 'function' ... ) return any; // 若是不是一個promise則返回一個新的promise return new Promise((resolve) => { resolve(any) }) } ··· }
因此實現promise關鍵點在於,理解promise只決議一次,有三個狀態(pending,fullFilled,rejected),then/catch支持鏈式調用(返回一個新的promise),而且理解then/catch本質是個事件註冊器,相似於then = subscribe('resolve', callback),理解這些本身手動實現一個promise仍是不難的。面試
最後的最後:剛開始寫技術文章,若是有錯漏但願可以不吝指出,若是以爲寫的還不錯點個贊是對我最大的支持,謝謝。promise
最後附上源碼連接:https://github.com/MinuteWong...異步