從零實現一個 Promise
Promise 作為由社群提出和實現的非同步程式設計解決方案,ES6 將其寫進了語言標準,統一了用法,原生提供了 Promise 物件。本文將剖析 Promise 內部標準,根據Promises/A+ 規範 從零實現一個 Promise。
Promise 建構函式
在 Promise 建構函式中,主要操作是初始化狀態和資料以及執行函式引數。
首先需要將狀態初始化為 pending,然後定義 Promise 的值以及回撥函式集。
const PENDING = 'pending' const RESOLVED = 'resolved' const REJECTED = 'rejected' function MyPromise(executor) { let self = this self.status = PENDING // Promise 狀態,初始狀態為 pending self.data = undefined// Promise 的值 self.onResolvedCallback = [] // Promise resolve 時的回撥函式集 self.onRejectedCallback = [] // Promise reject 時的回撥函式集 // 待完善,resolve 和 reject 函式 // 待完善,執行 executor 函式 }
在建構函式中,還需要執行由外部傳進來的 executor 函式,executor 函式中有兩個函式引數,分別為 resolve 和 reject 函式。
function MyPromise(executor) { let self = this self.status = PENDING // Promise 狀態,初始狀態為 pending self.data = undefined// Promise 的值 self.onResolvedCallback = [] // Promise resolve 時的回撥函式集 self.onRejectedCallback = [] // Promise reject 時的回撥函式集 function resolve(value) { // 當狀態為 pending 時,改變狀態為 resolved,儲存 Promise 值以及執行回撥函式集 if (self.status === PENDING) { self.status = RESOLVED self.data = value self.onResolvedCallback.map(cb => cb(value)) } } function reject(reason) { // 當狀態為 pending 時,改變狀態為 rejected,儲存 Promise 值以及執行回撥函式集 if (self.status === PENDING) { self.status = REJECTED self.data = reason self.onRejectedCallback.map(cb => cb(reason)) } } try { executor(resolve, reject) } catch (e) { // executor 函式執行中丟擲錯誤時該 Promise 應該被 reject reject(e) } }
executor 函式需要使用 try catch 包裹執行的原因則是在 executor 函式執行中可能會丟擲錯誤,當丟擲錯誤時則該 Promise 應該被 reject,如下情況:
// 該 Promise 應該被 reject new Promise(function(resolve, reject) { throw 2 })
then 方法
then 方法主要是根據 Promise 當前狀態處理相應的邏輯,返回一個新的 Promise,新 Promise 的狀態由原先 Promise 的狀態和 then 方法函式引數中的返回值決定。
當 then 方法執行時,該 Promise 的狀態是不確定的,所以需要對 Promise 的狀態進行判斷然後執行不同的操作,then 方法會返回一個新的 Promise。
MyPromise.prototype.then = function (onResolved, onRejected) { let self = this let promise2 // 如果 then 的引數不是 function,則我們需要賦予預設函式實現值的透傳 onResolved = typeof onResolved === 'function' ? onResolved : value => value onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason } if (self.status === RESOLVED) { return promise2 = new MyPromise((resolve, reject) => { }) } if (self.status === REJECTED) { return promise2 = new MyPromise((resolve, reject) => { }) } if (self.status === PENDING) { return promise2 = new MyPromise((resolve, reject) => { }) } }
then 方法會返回一個新的 Promise 後,新 Promise 的狀態由原先 Promise 的狀態和 then 方法函式引數中的返回值決定。
MyPromise.prototype.then = function (onResolved, onRejected) { let self = this let promise2 // 如果 then 的引數不是 function,則我們需要賦予預設函式實現值的透傳 onResolved = typeof onResolved === 'function' ? onResolved : value => value onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason } if (self.status === RESOLVED) { return promise2 = new MyPromise((resolve, reject) => { try { // 執行 onResolved 函式並獲取返回值。若返回值是 Promise 物件,則取它的結果作為 promise2 的結果,否則以返回值作為 promise2 的結果 var x = onResolved(self.data) if (x instanceof MyPromise) { x.then(resolve, reject) } resolve(x) } catch (e) { // 丟擲錯誤則以捕獲到的錯誤作為 promise2 的結果 reject(e) } }) } if (self.status === REJECTED) { return promise2 = new MyPromise((resolve, reject) => { try { // 執行 onRejected 函式並獲取返回值。若返回值是 Promise 物件,則取它的結果作為 promise2 的結果,否則以返回值作為 promise2 的結果 var x = onRejected(self.data) if (x instanceof MyPromise) { x.then(resolve, reject) } reject(x) } catch (e) { // 丟擲錯誤則以捕獲到的錯誤作為 promise2 的結果 reject(e) } }) } if (self.status === PENDING) { return promise2 = new MyPromise((resolve, reject) => { // 將回調函式存進回撥函式集 self.onResolvedCallback.push((value) => { try { var x = onResolved(self.data) if (x instanceof MyPromise) { x.then(resolve, reject) } resolve(x) } catch (e) { reject(e) } }) self.onRejectedCallback.push((reason) => { try { var x = onRejected(self.data) if (x instanceof MyPromise) { x.then(resolve, reject) } reject(x) } catch (e) { reject(e) } }) }) } }
在 then 方法中根據 Promise 的當前狀態分別執行了不同的操作。當狀態為 resolved 時,執行 onResolved 函式(then 方法第一個函式引數)並根據返回值確定 promise2 的狀態;當狀態為 rejected 時,執行 onRejected 函式(then 方法第二個函式引數)並根據返回值確定 promise2 的狀態;當狀態為 pending 時,則需要將 onResolved 和 onRejected 函式先存進回撥函式集中,等到 Promise 狀態改變後再執行。
而在程式碼註釋中說明,如果 then 的引數不是 function,則我們需要賦予預設函式實現值的透傳 。
當傳進 then 方法中 onResolved 或 onRejected 函式引數為空時,則應該賦予它們一個預設函式,該預設函式 return 或 throw 原先的引數值,這樣才能正確實現 then 方法的鏈式呼叫,如下:
new MyPromise((resolve, reject) => { resolve(1) }) .then() .then() .then((value) => { console.log(value) })
至此,我們便完成了一個符合 Promises/A+ 規範的 Promise 基礎版,同原生 Promise 一樣,可以通過如下方式使用:
let myPromise = new MyPromise((resolve, reject) => { if (/* 非同步操作成功 */) { resolve(value); } else { reject(error); } }); myPromise.then((value) => { console.log(value) }, (err) => { console.log(err) })
Promise 終極版
上述的程式碼已經能夠實現一個基本 Promise 的功能,而在實際使用過程中,我們還需要根據 Promises/A+ 規範繼續完善它。
需要完善的主要有以下兩點:
- 不同Promise 之間的相容;
- 非同步呼叫操作;
在實際中,有多種不同的 Promise 實現,關於不同 Promise 間的互動,Promises/A+ 規範 已經做了詳細的說明,其中詳細指定了如何通過 then 的實參的返回值來決定 promise2 的狀態 ,我們只需要按照標準將內容轉成程式碼即可。
function resolvePromise(promise2, x, resolve, reject) { var then var thenCalledOrThrow = false if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise!')) } if (x instanceof Promise) { if (x.status === 'pending') { x.then(function(value) { resolvePromise(promise2, value, resolve, reject) }, reject) } else { x.then(resolve, reject) } return } if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) { try { then = x.then if (typeof then === 'function') { then.call(x, function rs(y) { if (thenCalledOrThrow) return thenCalledOrThrow = true return resolvePromise(promise2, y, resolve, reject) }, function rj(r) { if (thenCalledOrThrow) return thenCalledOrThrow = true return reject(r) }) } else { resolve(x) } } catch (e) { if (thenCalledOrThrow) return thenCalledOrThrow = true return reject(e) } } else { resolve(x) } }
所以,在 then 方法中,我們不再需要判斷返回值 x 的型別,然後再根據 x 的型別去決定 promise2 的狀態,只需要將其傳入 resolvePromise 函式即可。
// self.status === RESOLVED 部分更改,其餘兩個狀態更改同理 var x = onResolved(self.data) if (x instanceof MyPromise) { x.then(resolve, reject) } resolve(x) => var x = onResolved(self.data) resolvePromise(promise2, x, resolve, reject)
最後,在標準中,說明了某些地方需要使用非同步呼叫,在我們的實現中,我們需要在 resolve、reject、onResolved、onRejected 加上非同步呼叫的程式碼,這裡我們使用 setTimeout(fn, 0) 來實現。
至此,我們實現了一個符合Promises/A+ 規範的終極版 Promise,如下:
const PENDING = 'pending' const RESOLVED = 'resolved' const REJECTED = 'rejected' function MyPromise(executor) { let self = this self.status = PENDING // Promise 狀態,初始狀態為 pending self.data = undefined// Promise 的值 self.onResolvedCallback = [] // Promise resolve 時的回撥函式集 self.onRejectedCallback = [] // Promise reject 時的回撥函式集 function resolve(value) { setTimeout(() => { // 非同步回撥 // 當狀態為 pending 時,改變狀態為 resolved,儲存 Promise 值以及執行回撥函式集 if (self.status === PENDING) { self.status = RESOLVED self.data = value self.onResolvedCallback.map(cb => cb(value)) } }, 0) } function reject(reason) { setTimeout(() => { // 非同步回撥 // 當狀態為 pending 時,改變狀態為 rejected,儲存 Promise 值以及執行回撥函式集 if (self.status === PENDING) { self.status = REJECTED self.data = reason self.onRejectedCallback.map(cb => cb(reason)) } }, 0) } try { executor(resolve, reject) } catch (e) { // executor 函式執行中丟擲錯誤時該 Promise 應該被 reject reject(e) } } MyPromise.prototype.then = function (onResolved, onRejected) { let self = this let promise2 // 如果 then 的引數不是 function,則我們需要賦予預設函式實現值的透傳 onResolved = typeof onResolved === 'function' ? onResolved : value => value onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason } if (self.status === RESOLVED) { return promise2 = new MyPromise((resolve, reject) => { setTimeout(() => { // 非同步回撥 try { // 執行 onResolved 函式並獲取返回值。若返回值是 Promise 物件,則取它的結果作為 promise2 的結果,否則以返回值作為 promise2 的結果 var x = onResolved(self.data) resolvePromise(promise2, x, resolve, reject) } catch (e) { // 丟擲錯誤則以捕獲到的錯誤作為 promise2 的結果 reject(e) } }, 0) }) } if (self.status === REJECTED) { return promise2 = new MyPromise((resolve, reject) => { setTimeout(() => { // 非同步回撥 try { // 執行 onRejected 函式並獲取返回值。若返回值是 Promise 物件,則取它的結果作為 promise2 的結果,否則以返回值作為 promise2 的結果 var x = onRejected(self.data) resolvePromise(promise2, x, resolve, reject) } catch (e) { // 丟擲錯誤則以捕獲到的錯誤作為 promise2 的結果 reject(e) } }, 0) }) } if (self.status === PENDING) { return promise2 = new MyPromise((resolve, reject) => { // 將回調函式存進回撥函式集 self.onResolvedCallback.push((value) => { try { var x = onResolved(self.data) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) self.onRejectedCallback.push((reason) => { try { var x = onRejected(self.data) resolvePromise(promise2, x, resolve, reject) } catch (e) { reject(e) } }) }) } } function resolvePromise(promise2, x, resolve, reject) { var then var thenCalledOrThrow = false if (promise2 === x) { return reject(new TypeError('Chaining cycle detected for promise!')) } if (x instanceof Promise) { if (x.status === 'pending') { x.then(function (value) { resolvePromise(promise2, value, resolve, reject) }, reject) } else { x.then(resolve, reject) } return } if ((x !== null) && ((typeof x === 'object') || (typeof x === 'function'))) { try { then = x.then if (typeof then === 'function') { then.call(x, function rs(y) { if (thenCalledOrThrow) return thenCalledOrThrow = true return resolvePromise(promise2, y, resolve, reject) }, function rj(r) { if (thenCalledOrThrow) return thenCalledOrThrow = true return reject(r) }) } else { resolve(x) } } catch (e) { if (thenCalledOrThrow) return thenCalledOrThrow = true return reject(e) } } else { resolve(x) } }