最近老是聽到前端面試要求手寫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
最後附上源碼連接:github.com/MinuteWong/…異步
歡迎閱讀個人其餘文章:函數