使用JavaScript按部就班實現一個簡單的Promise,支持異步和then鏈式調用。
翻譯並整理自 Medium: Implementing a simple Promise in Javascript - by Zhi Sun
在前端面試和平常開發中,常常會接觸到Promise。而且在現現在的不少面試中,也會常常被要求手寫Promise。javascript
接下來,將使用JavaScript按部就班實現一個簡單的Promise,支持異步和then鏈式調用。前端
Promise對象用於表示一個異步操做的最終完成 (或失敗)及其結果值,經常使用來實現異步操做。java
Promise有三種狀態:面試
pending數組
初始狀態promise
fulfilled異步
執行成功後的狀態函數
rejected測試
執行失敗後的狀態this
Promise狀態只能由pending
改變爲fulfilled
或者由pending
改變爲rejected
,Promise狀態改變的這一過程被稱爲settled
,而且,狀態一旦改變,後續就不會再次被改變。
Promise構造函數接收一個函數參數executor
,該函數接收兩個參數:
執行resolve
會將Promise狀態由pending
改變爲fulfilled
,並觸發then
方法中的成功回調函數onFulfilled
,
執行reject
會將Promise狀態由pending
改變爲rejected
,並觸發then
方法中的失敗回調函數onRejected
。
then
方法接收兩個參數:
onFulfilled
成功回調函數,接收一個參數,即resolve
函數中傳入的值
onRejected
失敗回調函數,接收一個參數,即reject
函數中傳入的值
若是Promise狀態變爲fulfilled
,就會執行成功回調函數onFulfilled
;若是Promise狀態變爲rejected
,就會執行失敗回調函數onRejected
。
首先,constructor
接收一個函數executor
,該函數又接收兩個參數,分別是resolve
和reject
函數。
所以,須要在constructor
中建立resolve
和reject
函數,並傳入executor
函數中。
class MyPromise { constructor(executor) { const resolve = (value) => {}; const reject = (reason) => {}; try { executor(resolve, reject); } catch (err) { reject(err); } } }
其次,Promise會根據狀態,執行對應的回調函數。最開始的狀態爲pending
,當resolve
時,狀態由pending
變爲fulfilled
;當reject
時,狀態由pending
變爲rejected
。而且,一旦狀態變動後,就不會再次變動。
class MyPromise { constructor(executor) { this.state = 'pending'; const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; } }; const reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected'; } }; try { executor(resolve, reject); } catch (err) { reject(err); } } }
Promise狀態變動後,會觸發then
方法中對應的回調函數。若是狀態由pending
變爲fulfilled
,則會觸發成功回調函數,若是狀態由pending
變爲rejected
,則會觸發失敗回調函數。
class MyPromise { constructor(executor) { this.state = 'pending'; this.value = null; const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; } }; const reject = (reason) => { if (this.state === 'pending') { this.state = 'rejected'; this.value = reason; } }; try { executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { if (this.state === 'fulfilled') { onFulfilled(this.value); } if (this.state === 'rejected') { onRejected(this.value); } } }
接下來能夠寫點測試代碼測試一下功能
const p1 = new MyPromise((resolve, reject) => resolve('resolved')); p1.then( (res) => console.log(res), // resolved (err) => console.log(err) ); const p2 = new MyPromise((resolve, reject) => reject('rejected')); p2.then( (res) => console.log(res), (err) => console.log(err) // rejected );
可是,若是用如下代碼測試,會發現什麼也沒有輸出。
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => resolve('resolved'), 1000); }); p1.then( (res) => console.log(res), (err) => console.log(err) ); const p2 = new MyPromise((resolve, reject) => { setTimeout(() => reject('rejected'), 1000); }); p2.then( (res) => console.log(res), (err) => console.log(err) );
這是由於在調用then
方法時,Promise仍處於pending
狀態。onFulfilled
和onRejected
回調函數都沒有被執行。
所以,接下來須要支持異步。
爲了支持異步,須要先保存onFulfilled
和onRejected
回調函數,一旦Promise狀態變化,馬上執行對應的回調函數。
⚠:這裏有個細節須要注意,即onFulfilledCallbacks
和onRejectedCallbacks
是數組,由於Promise可能會被調用屢次,所以會存在多個回調函數。
class MyPromise { constructor(executor) { this.state = 'pending'; this.value = null; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onFulfilledCallbacks.forEach((fn) => fn(value)); } }; const reject = (value) => { if (this.state === 'pending') { this.state = 'rejected'; this.value = value; this.onRejectedCallbacks.forEach((fn) => fn(value)); } }; try { executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { if (this.state === 'pending') { this.onFulfilledCallbacks.push(onFulfilled); this.onRejectedCallbacks.push(onRejected); } if (this.state === 'fulfilled') { onFulfilled(this.value); } if (this.state === 'rejected') { onRejected(this.value); } } }
接下來能夠用以前的測試代碼測試一下功能
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => resolve('resolved'), 1000); }); p1.then( (res) => console.log(res), // resolved (err) => console.log(err) ); const p2 = new MyPromise((resolve, reject) => { setTimeout(() => reject('rejected'), 1000); }); p2.then( (res) => console.log(res), (err) => console.log(err) // rejected );
可是若是用如下代碼測試,會發現報錯了。
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => resolve('resolved'), 1000); }); p1.then( (res) => console.log(res), (err) => console.log(err) ).then( (res) => console.log(res), (err) => console.log(err) ); // Uncaught TypeError: Cannot read property 'then' of undefined
這是由於第一個then
方法並無返回任何值,然而卻又連續調用了then
方法。
所以,接下來須要實現then
鏈式調用。
then
鏈式調用的Promise要想支持then
鏈式調用,then
方法須要返回一個新的Promise。
所以,須要改造一下then
方法,返回一個新的Promise,等上一個Promise的onFulfilled
或onRejected
回調函數執行完成後,再執行新的Promise的resolve
或reject
函數。
class MyPromise { then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { if (this.state === 'pending') { this.onFulfilledCallbacks.push(() => { try { const fulfilledFromLastPromise = onFulfilled(this.value); resolve(fulfilledFromLastPromise); } catch (err) { reject(err); } }); this.onRejectedCallbacks.push(() => { try { const rejectedFromLastPromise = onRejected(this.value); reject(rejectedFromLastPromise); } catch (err) { reject(err); } }); } if (this.state === 'fulfilled') { try { const fulfilledFromLastPromise = onFulfilled(this.value); resolve(fulfilledFromLastPromise); } catch (err) { reject(err); } } if (this.state === 'rejected') { try { const rejectedFromLastPromise = onRejected(this.value); reject(rejectedFromLastPromise); } catch (err) { reject(err); } } }); } }
接下來能夠用如下代碼測試一下功能
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => resolve('resolved'), 1000); }); p1.then( (res) => { console.log(res); // resolved return res; }, (err) => console.log(err) ).then( (res) => console.log(res), // resolved (err) => console.log(err) ); const p2 = new MyPromise((resolve, reject) => { setTimeout(() => reject('rejected'), 1000); }); p2.then( (res) => console.log(res), (err) => { console.log(err); // rejected throw new Error('rejected'); } ).then( (res) => console.log(res), (err) => console.log(err) // Error: rejected );
可是,若是改用如下代碼測試,會發現第二個then
方法中的成功回調函數並無按預期輸出(‘resolved’),而是輸出了上一個then
方法的onFulfilled
回調函數中返回的Promise。
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => resolve('resolved'), 1000); }); p1.then( (res) => { console.log(res); // resolved return new MyPromise((resolve, reject) => { setTimeout(() => resolve('resolved'), 1000); }) }, (err) => console.log(err) ).then( (res) => console.log(res), // MyPromise {state: "pending"} (err) => console.log(err) );
這是由於onFulfilled
/onRejected
回調函數執行完以後,只是簡單的將onFulfilled
/onRejected
執行完返回的值傳入resolve
/reject
函數中執行,並無考慮onFulfilled
/onRejected
執行完會返回一個新的Promise這種狀況,因此第二次then
方法的成功回調函數中直接輸出了上一次then
方法的成功回調函數中返回的Promise。所以,接下來須要解決這個問題。
首先,能夠將以上測試代碼改爲另外一種寫法,方便梳理思路。
const p1 = new MyPromise((resolve, reject) => { setTimeout(() => resolve('resolved'), 1000); }); const p2 = p1.then( (res) => { console.log(res); const p3 = new MyPromise((resolve, reject) => { setTimeout(() => resolve('resolved'), 1000); }); return p3; }, (err) => console.log(err) ); p2.then( (res) => console.log(res), (err) => console.log(err) );
能夠看到,一共有三個Promise:
第一個Promise
即經過new
構造出來的p1
第二個Promise
即經過調用then
方法返回的p2
第三個Promise
即在p1.then
方法的成功回調函數參數中返回的p3
如今的問題是,調用p2的then
方法時,p3還處於pending
狀態。
要想實現p2.then
方法中的回調函數能正確輸出p3中resolve
/reject
以後的值,須要先等p3狀態變化後再將變化後的值傳入p2中的resolve
/reject
中便可。換句話說,三個Promise狀態變化的前後順序應該是p1 --> p3 --> p2。
class MyPromise { then(onFulfilled, onRejected) { return new MyPromise((resolve, reject) => { if (this.state === 'pending') { this.onFulfilledCallbacks.push(() => { try { const fulfilledFromLastPromise = onFulfilled(this.value); if (fulfilledFromLastPromise instanceof MyPromise) { fulfilledFromLastPromise.then(resolve, reject); } else { resolve(fulfilledFromLastPromise); } } catch (err) { reject(err); } }); this.onRejectedCallbacks.push(() => { try { const rejectedFromLastPromise = onRejected(this.value); if (rejectedFromLastPromise instanceof MyPromise) { rejectedFromLastPromise.then(resolve, reject); } else { reject(rejectedFromLastPromise); } } catch (err) { reject(err); } }); } if (this.state === 'fulfilled') { try { const fulfilledFromLastPromise = onFulfilled(this.value); if (fulfilledFromLastPromise instanceof MyPromise) { fulfilledFromLastPromise.then(resolve, reject); } else { resolve(fulfilledFromLastPromise); } } catch (err) { reject(err); } } if (this.state === 'rejected') { try { const rejectedFromLastPromise = onRejected(this.value); if (rejectedFromLastPromise instanceof MyPromise) { rejectedFromLastPromise.then(resolve, reject); } else { reject(rejectedFromLastPromise); } } catch (err) { reject(err); } } }); } }
最後,一個簡單的Promise就完成了,支持異步和then
鏈式調用。完整代碼以下:
class MyPromise { constructor(executor) { this.state = 'pending'; this.value = null; this.onFulfilledCallbacks = []; this.onRejectedCallbacks = []; const resolve = (value) => { if (this.state === 'pending') { this.state = 'fulfilled'; this.value = value; this.onFulfilledCallbacks.forEach((fn) => fn(value)); } }; const reject = (value) => { if (this.state === 'pending') { this.state = 'rejected'; this.value = value; this.onRejectedCallbacks.forEach((fn) => fn(value)); } }; try { executor(resolve, reject); } catch (err) { reject(err); } } then(onFulfilled, onRejected) { return new Promise((resolve, reject) => { if (this.state === 'pending') { this.onFulfilledCallbacks.push(() => { try { const fulfilledFromLastPromise = onFulfilled(this.value); if (fulfilledFromLastPromise instanceof Promise) { fulfilledFromLastPromise.then(resolve, reject); } else { resolve(fulfilledFromLastPromise); } } catch (err) { reject(err); } }); this.onRejectedCallbacks.push(() => { try { const rejectedFromLastPromise = onRejected(this.value); if (rejectedFromLastPromise instanceof Promise) { rejectedFromLastPromise.then(resolve, reject); } else { reject(rejectedFromLastPromise); } } catch (err) { reject(err); } }); } if (this.state === 'fulfilled') { try { const fulfilledFromLastPromise = onFulfilled(this.value); if (fulfilledFromLastPromise instanceof Promise) { fulfilledFromLastPromise.then(resolve, reject); } else { resolve(fulfilledFromLastPromise); } } catch (err) { reject(err); } } if (this.state === 'rejected') { try { const rejectedFromLastPromise = onRejected(this.value); if (rejectedFromLastPromise instanceof Promise) { rejectedFromLastPromise.then(resolve, reject); } else { reject(rejectedFromLastPromise); } } catch (err) { reject(err); } } }); } }