46. ES6+ 實現一個簡版的 Promise
1. 前言
上一節我們學習了 ES6 Promise的基本用法,并且我們知道 Promise 最早出現在社區,所以ES6 中 Promise 也是遵循一個標準的規范的。這個規范就是?Promise A+ 規范?也就是任何人都可以遵循這個規范實現一個自己的 Promise,由于每個人實現的方式有所差異,Promise A+ 規范給出了一些要求和兼容方式。
本節我們將根據 Promise A+ 規范 實現一個簡版的 Promise API。
2. 實現步驟
上一節我們已經知道了 Promise 是一個類,默認接收一個參數 executor(執行器),并且會立即執行。所以首先需要創建一個 Promise 的類,然后傳入一個回調函數并執行它,故有如下的初始代碼:
class Promise { constructor(executor) { executor(); } }
Promise 有三個狀態:等待(padding)、成功(fulfilled),失?。╮ejected)。默認是等待狀態,等待態可以突變為成功態或失敗態,所以我們可以定義三個常量來存放這三個狀態。
const PENDING = 'PENDING'; const RESOLVED = 'RESOLVED'; // 成功態 const REJECTED = 'REJECTED'; // 失敗態 class Promise { constructor(executor) { this.status = PENDING; // 默認是等待態 executor(); } }
這樣我們就知道了 Promise 的基本狀態,那內部的狀態是怎么突變為成功或失敗的呢?這里執行器(executor)會提供兩個個方法用于改變 Promise 的狀態,所以我們需要在初始化時定義 resolve 和 reject 方法:在成功的時候會傳入成功的值,在失敗的時候會傳入失敗的原因。并且每個Promise 都會提供 then方法用于鏈式調用。
class Promise { constructor(executor) { this.status = PENDING; const resolve = (value) => {}; const reject = (reason) => {}; // 執行executor時,傳入成功或失敗的回調 executor(resolve, reject); } then(onfulfilled, onrejected) { } }
這時我們就可以開始著手去更改 Promise的狀態了,由于默認情況下 Promise 的狀態只能從 pending 到 fulfilled 和 rejected 的轉化。
class Promise { constructor(executor) { this.status = PENDING; const resolve = (value) => { // 只有等待態時才能更改狀態 if (this.status === PENDING) { this.status = RESOLVED; } }; const reject = (reason) => { if (this.status === PENDING) { this.status = REJECTED; } }; executor(resolve, reject); } ... }
成功和失敗都會返回對應的結果,所以我們需要定義成功的值和失敗的原因兩個全局變量,用于存放返回的結果。
class Promise { constructor(executor) { this.status = PENDING; this.value = undefined; this.reason = undefined; const resolve = (value) => { // 只有等待態時才能更改狀態 if (this.status === PENDING) { this.value = value; this.status = RESOLVED; } }; const reject = (reason) => { if (this.status === PENDING) { this.reason = reason; this.status = REJECTED; } }; executor(resolve, reject); } ... }
這時我們就已經為執行器提供了兩個回調函數了,如果在執行器執行時拋出異常時,我們需要使用 try…catch
來補貨一下。由于是拋出異常,所以,需要調用 reject
方法來修改為失敗的狀態。
try { executor(resolve, reject); } catch(e) { reject(e) }
我們知道實例在調用 then 方法時會傳入兩個回調函數 onfulfilled,,onrejected 去執行成功或失敗的回調,所以根據狀態會調用對應的函數來處理。
then(onfulfilled, onrejected) { if (this.status === RESOLVED) { onfulfilled(this.value) } if (this.status === REJECTED) { onrejected(this.reason) } }
這樣我們就完了 Promise 最基本的同步功能
let promise = new Promise((resolve, reject) => { resolve('value'); // throw new Error('錯誤'); // reject('error reason') // setTimeout(() => { // resolve('value'); // }, 1000) }) promise.then((data) => { console.log('resolve response', data); }, (err) => { console.log('reject response', err); })
用上面的代碼對我們寫的 Promise 進行驗證,通過測試用例可知,我們寫的 Promise 只能在同步中運行,當我們使用 setTimeout 異步去返回時,并沒有預想的在then的成功回調中打印結果。
對于這種異步行為需要專門處理,如何處理異步的內容呢?我們知道在執行異步任務時 Promise 的狀態并沒有被改變,也就是并沒有執行 resolve 或 reject 方法,但是 then 中的回調已經執行了,這時就需要增加當 Promise 還是等待態的邏輯,在等待態時把回調函數都存放起來,等到執行 resolve 或 reject 再依次執行之前存放的then的回調函數,也就是我們平時用到的發布訂閱模式。
實現步驟:
- 首先,需要在初始化中增加存放成功的回調函數和存放失敗的回調函數;
- 然后,由于是異步執行 resolve 或 reject 所以需要在 then 方法中把回調函數存放起來;
- 最后,當執行 resolve 或 reject 時取出存放的回調函數依次執行。
根據以上的實現步驟可以得到如下的代碼:
class Promise { constructor(executor) { this.status = PENDING; this.value = undefined; // 成功的值 this.reason = undefined; // 失敗的原因 // 存放成功的回調函數 this.onResolvedCallbacks = []; // 存放失敗的回調函數 this.onRejectedCallbacks = []; let resolve = (value) => { if (this.status === PENDING) { this.value = value; this.status = RESOLVED; // 異步時,存放在成功的回調函數依次執行 this.onResolvedCallbacks.forEach(fn => fn()) } }; let reject = (reason) => { if (this.status === PENDING) { this.value = reason; this.status = REJECTED; // 異步時,存放在失敗的回調函數依次執行 this.onRejectedCallbacks.forEach(fn => fn()) } }; try { executor(resolve, reject); } catch(e) { reject(e) } } then(onfulfilled, onrejected) { if (this.status === RESOLVED) { onfulfilled(this.value) } if (this.status === REJECTED) { onrejected(this.reason) } if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { // TODO onfulfilled(this.value); }) this.onRejectedCallbacks.push(() => { // TODO onrejected(this.reason); }) } } }
上面的代碼中,在存放回調函數時把?onfulfilled
,?onrejected
?存放在一個函數中執行,這樣的好處是可以在前面增加處理問題的邏輯。這樣我們就完成了處理異步的 Promise 邏輯。下面是測試用例,可以正常的執行 then 的成功回調函數。
let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('100'); }, 1000) }) promise.then((data) => { console.log('resolve response:', data); // resolve response: 100 }, (err) => { console.log('reject response:', err); })
到這里我們是不是已經基本實現了 Promise 的功能呢?ES6 中的 then 方法支持鏈式調用,那我們寫的可以嗎?我們在看下面的一個測試用例:
let promise = new Promise((resolve, reject) => { setTimeout(() => { resolve('100'); }, 1000) }) promise.then((data) => { console.log('resolve response:', data); // resolve response: 100 return 200 }, (err) => { console.log('reject response:', err); }).then((data) => { console.log('data2:', data) }, null) // TypeError: Cannot read property 'then' of undefined
然而當我們在執行的時候會報錯,then 是 undefined。為什么會這樣呢?那我們要知道如何滿足鏈式調用的規范,那就是在完成任務后再返回一個Promise 實例。那如何返回一個 Promise 實例呢?在 Promise A+ 規范的 2.2.7 小節在有詳細的描述,再實例化一個 promise2 來存放執行后的結果,并返回 promise2。那么我們就要改造 then 方法了。
class Promise { ... then(onfulfilled, onrejected) { let promise2 = new Promise((resolve, reject) => { if (this.status === RESOLVED) { const x = onfulfilled(this.value) resolve(x) } if (this.status === REJECTED) { const x = onrejected(this.reason); reject(x) } if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { const x = onfulfilled(this.value) resolve(x) }) this.onRejectedCallbacks.push(() => { const x = onrejected(this.reason); reject(x) }) } }) return promise2 } }
再使用上面的測試用例,就可以得到正確的結果:
let promise = new Promise((resolve, reject) => { resolve('100'); }) promise.then((data) => { console.log('data1:', data); // data1: 100 return 200 }, null).then((data) => { console.log('data2:', data); // data2: 200 throw new Error('error') }, null).then(null, () => { consol.log('程序報錯...') })
上面的測試用例中,當 then 的回調函數拋出異常時需要去捕獲錯誤,傳到下一個 then 的失敗回調函數中。
class Promise { ... then(onfulfilled, onrejected) { let promise2 = new Promise((resolve, reject) => { if (this.status === RESOLVED) { try{ const x = onfulfilled(this.value) resolve(x) } catch(e) { reject(e) } } if (this.status === REJECTED) { try{ const x = onrejected(this.reason); resolve(x) } catch(e) { reject(e) } } if (this.status === PENDING) { this.onResolvedCallbacks.push(() => { try{ const x = onfulfilled(this.value) resolve(x) } catch(e) { reject(e) } }) this.onRejectedCallbacks.push(() => { try{ const x = onrejected(this.reason); resolve(x) } catch(e) { reject(e) } }) } }) return promise2 } }
到這里為止我們就已經實現了一個簡版的 Promise,因為Promise是一個規范,很多人都可以實現自己的 Promise 所以 Promise A+ 規范做了很多兼容處理的要求,如果想實現一個完整的 Promise 可以參考?Promise A+ 規范?。
3. 小結
本節主要按照?Promise A+ 規范?部分的要求實現了一個簡版的 Promise API 這個 Promise 基本上滿足同步異步的鏈式調用,對基本的異常做了處理。當然?Promise A+ 規范?所規定的細節比較多,剩下的都是對各種異常錯誤的處理,所以后面我們也沒有去實現。另外官網下提供了一個測試用例來驗證我們寫的 Promise 是否符合?Promise A+ 規范?,所以可以參考?promises-tests?這個庫來完成我們的 Promise 的測試。
1. 本站所有文章教程及資源素材均來源于網絡與用戶分享或為本站原創,僅限用于學習和研究。
2. 如果內容損害你的權益請聯系客服QQ:1642748312給予處理。
碼云筆記 » 46. ES6+ 實現一個簡版的 Promise