從0到1實現Promise
Promise大家一定都不陌生了,JavaScript非同步流程從最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果對於這些不熟悉的可以參考我另一篇文章《JavaScript非同步程式設計》),這不僅僅是技術實現的發展,更是思想上對於如何控制非同步的遞進。Promise作為後續方案的基礎,是重中之重,也是面試時候最常被問到的。
今天我們就一起從0到1實現一個基於A+規範的Promise,過程中也會對Promise的異常處理,以及是否可手動終止做一些討論,最後會對我們實現的Promise做單元測試。完整的程式碼已經上傳到github,想直接看程式碼的可以 ofollow,noindex">點這裡 。
雖然已經有很多帶你實現Promise類的文章了,但每個人理解的程度不一樣,也許不同的文章可以帶給你不同的思考呢,那我們就開始吧。
正文
1. 基礎框架
new Promise()時接收一個executor函式作為引數,該函式會立即執行,函式中有兩個引數,它們也是函式,分別是resolve和reject,函式同步執行一定要放在try...catch中,否則無法進行錯誤捕獲。
MyPromise.js
function MyPromise(executor) { function resolve(value) { } function reject(reason) { } try { executor(resolve, reject); } catch (reason) { reject(reason); } } module.exports = MyPromise; 複製程式碼
resolve()接收Promise成功值value,reject接收Promise失敗原因reason。
test.js
let MyPromise = require('./MyPromise.js'); let promise = new MyPromise(function(resolve, reject) { resolve(123); }) 複製程式碼
2. 新增狀態機
目前實現存在的問題:
- Promise是一個狀態機的機制,初始狀態為
pending
,成功狀態為fulfilled
,失敗狀態為rejected
。只能從pending
->fulfilled
,或者從pending
->rejected
,並且狀態一旦轉變,就永遠不會再變了。
所以,我們需要為Promise新增一個狀態流轉的機制。
MyPromise.js
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function MyPromise(executor) { let self = this; self.state = PENDING; function resolve(value) { if (self.state === PENDING) { self.state = FULFILLED; } } function reject(reason) { if (self.state === PENDING) { self.state = REJECTED; } } try { executor(resolve, reject); } catch (reason) { reject(reason); } } module.exports = MyPromise; 複製程式碼
test.js
let MyPromise = require('./MyPromise.js'); let promise = new MyPromise(function(resolve, reject) { resolve(123); }); promise.then(function(value) { console.log('value', value); }, function(reason) { console.log('reason', reason); }) 複製程式碼
3. 新增 then
方法
Promise擁有一個 then
方法,接收兩個函式 onFulfilled
和 onRejected
,分別作為Promise成功和失敗的回撥。所以,在 then
方法中我們需要對狀態 state
進行判斷,如果是 fulfilled
,則執行 onFulfilled(value)
方法,如果是 rejected
,則執行 onRejected(reason)
方法。
由於成功值 value
和失敗原因 reason
是由使用者在 executor
中通過 resolve(value)
和 reject(reason)
傳入的,所以我們需要有一個全域性的 value
和 reason
供後續方法獲取。
MyPromise.js
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function MyPromise(executor) { let self = this; self.state = PENDING; self.value = null; self.reason = null; function resolve(value) { if (self.state === PENDING) { self.state = FULFILLED; self.value = value; } } function reject(reason) { if (self.state === PENDING) { self.state = REJECTED; self.reason = reason; } } try { executor(resolve, reject); } catch (reason) { reject(reason); } } MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; if (self.state === FULFILLED) { onFuifilled(self.value); } if (self.state === REJECTED) { onRejected(self.reason); } }; module.exports = MyPromise; 複製程式碼
4. 實現非同步呼叫resolve
目前實現存在的問題:
- 同步呼叫
resolve()
沒有問題,但如果是非同步呼叫,比如放到setTimeout
中,因為目前的程式碼在呼叫then()
方法時,state
仍是pending
狀態,當timer到時候呼叫resolve()
把state
修改為fulfilled
狀態,但是onFulfilled()
函式已經沒有時機呼叫了。
針對上述問題,進行如下修改:
MyPromise.js
const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected'; function MyPromise(executor) { let self = this; self.state = PENDING; self.value = null; self.reason = null; self.onFulfilledCallbacks = []; self.onRejectedCallbacks = []; function resolve(value) { if (self.state === PENDING) { self.state = FULFILLED; self.value = value; self.onFulfilledCallbacks.forEach(function(fulfilledCallback) { fulfilledCallback(); }); } } function reject(reason) { if (self.state === PENDING) { self.state = REJECTED; self.reason = reason; self.onRejectedCallbacks.forEach(function(rejectedCallback) { rejectedCallback(); }); } } try { executor(resolve, reject); } catch (reason) { reject(reason); } } MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; if (self.state === PENDING) { self.onFulfilledCallbacks.push(() => { onFuifilled(self.value); }); self.onRejectedCallbacks.push(() => { onRejected(self.reason); }); } if (self.state === FULFILLED) { onFuifilled(self.value); } if (self.state === REJECTED) { onRejected(self.reason); } }; module.exports = MyPromise; 複製程式碼
我們添加了兩個回撥函式陣列 onFulfilledCallbacks
和 onRejectedCallbacks
,用來儲存 then()
方法中傳入的成功和失敗回撥。然後,當用戶呼叫 resolve()
或 reject()
的時候,修改 state
狀態,並從相應的回撥陣列中依次取出回撥函式執行。
同時,通過這種方式我們也實現了可以註冊多個 then()
函式,並且在成功或者失敗時按照註冊順序依次執行。
test.js
let MyPromise = require('./MyPromise.js'); let promise = new MyPromise(function(resolve, reject) { setTimeout(function() { resolve(123); }, 1000); }); promise.then(function(value) { console.log('value1', value); }, function(reason) { console.log('reason1', reason); }); promise.then(function(value) { console.log('value2', value); }, function(reason) { console.log('reason2', reason); }); 複製程式碼
5. then返回的仍是Promise
讀過PromiseA+規範的同學肯定知道, then()
方法返回的仍是一個Promise,並且返回Promise的 resolve
的值是上一個Promise的 onFulfilled()
函式或 onRejected()
函式的返回值。如果在上一個Promise的 then()
方法回撥函式的執行過程中發生了錯誤,那麼會將其捕獲到,並作為返回的Promise的 onRejected
函式的引數傳入。比如:
let promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((value) => { console.log('value1', value); return 456; }).then((value) => { console.log('value2', value); }); let promise = new Promise((resolve, reject) => { resolve(123); }); 複製程式碼
列印結果為:
value1 123value2 456
let promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((value) => { console.log('value1', value); a.b = 2;// 這裡存在語法錯誤 return 456; }).then((value) => { console.log('value2', value); }, (reason) => { console.log('reason2', reason); }); 複製程式碼
列印結果為:
value1 123reason2 ReferenceError: a is not defined
可以看到, then()
方法回撥函式如果發生錯誤,會被捕獲到,那麼 then()
返回的Promise會自動變為 onRejected
,執行 onRejected()
回撥函式。
let promise = new Promise((resolve, reject) => { reject(123); }); promise.then((value) => { console.log('value1', value); return 456; }, (reason) => { console.log('reason1', reason); return 456; }).then((value) => { console.log('value2', value); }, (reason) => { console.log('reason2', reason); }); 複製程式碼
列印結果為:
reason1 123value2 456
好啦,接下來我們就去實現 then()
方法依然返回一個Promise。
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; let promise2 = null; promise2 = new MyPromise((resolve, reject) => { if (self.state === PENDING) { self.onFulfilledCallbacks.push(() => { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch(reason) { reject(reason); } }); self.onRejectedCallbacks.push(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch(reason) { reject(reason); } }); } if (self.state === FULFILLED) { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } } if (self.state === REJECTED) { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } } }); return promise2; }; 複製程式碼
可以看到,我們新增了一個 promise2
作為 then()
方法的返回值。通過 let x = onFuifilled(self.value)
或者 let x = onRejected(self.reason)
拿到 then()
方法回撥函式的返回值,然後呼叫 self.resolvePromise(promise2, x, resolve, reject)
,將新增的 promise2
、 x
、 promise2
的 resolve
和 reject
傳入到 resolvePromise()
中。
所以,下面我們重點看一下 resolvePromise()
方法。
MyPromise.js
MyPromise.prototype.resolvePromise = function(promise2, x, resolve, reject) { let self = this; let called = false;// called 防止多次呼叫 if (promise2 === x) { return reject(new TypeError('迴圈引用')); } if (x !== null && (Object.prototype.toString.call(x) === '[object Object]' || Object.prototype.toString.call(x) === '[object Function]')) { // x是物件或者函式 try { let then = x.then; if (typeof then === 'function') { then.call(x, (y) => { // 別人的Promise的then方法可能設定了getter等,使用called防止多次呼叫then方法 if (called) return ; called = true; // 成功值y有可能還是promise或者是具有then方法等,再次resolvePromise,直到成功值為基本型別或者非thenable self.resolvePromise(promise2, y, resolve, reject); }, (reason) => { if (called) return ; called = true; reject(reason); }); } else { if (called) return ; called = true; resolve(x); } } catch (reason) { if (called) return ; called = true; reject(reason); } } else { // x是普通值,直接resolve resolve(x); } }; 複製程式碼
resolvePromise()
是用來解析 then()
回撥函式中返回的仍是一個 Promise
,這個 Promise
有可能是我們自己的,有可能是別的庫實現的,也有可能是一個具有 then()
方法的物件,所以這裡靠 resolvePromise()
來實現統一處理。
下面是翻譯自PromiseA+規範關於 resolvePromise()
的要求:
Promise 解決過程
Promise 解決過程是一個抽象的操作,其需輸入一個 promise 和一個值,我們表示為 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一個 Promise ,解決程式即嘗試使 promise 接受 x 的狀態;否則其用 x 的值來執行 promise 。
這種 thenable 的特性使得 Promise 的實現更具有通用性:只要其暴露出一個遵循 Promise/A+ 協議的 then 方法即可;這同時也使遵循 Promise/A+ 規範的實現可以與那些不太規範但可用的實現能良好共存。
執行 [[Resolve]](promise, x) 需遵循以下步驟:
-
x 與 promise 相等
如果 promise 和 x 指向同一物件,以 TypeError 為據因拒絕執行 promise
-
x 為 Promise
如果 x 為 Promise ,則使 promise 接受 x 的狀態:
- 如果 x 處於等待態, promise 需保持為等待態直至 x 被執行或拒絕
- 如果 x 處於執行態,用相同的值執行 promise
- 如果 x 處於拒絕態,用相同的據因拒絕 promise
-
x 為物件或函式 如果 x 為物件或者函式:
- 把 x.then 賦值給 then
- 如果取 x.then 的值時丟擲錯誤 e ,則以 e 為據因拒絕 promise
- 如果 then 是函式,將 x 作為函式的作用域 this 呼叫之。傳遞兩個回撥函式作為引數,第一個引數叫做 resolvePromise ,第二個引數叫做 rejectPromise:
- 如果 resolvePromise 以值 y 為引數被呼叫,則執行 [[Resolve]](promise, y)
- 如果 rejectPromise 以據因 r 為引數被呼叫,則以據因 r 拒絕 promise
- 如果 resolvePromise 和 rejectPromise 均被呼叫,或者被同一引數呼叫了多次,則優先採用首次呼叫並忽略剩下的呼叫
- 如果呼叫 then 方法丟擲了異常 e:
- 如果 resolvePromise 或 rejectPromise 已經被呼叫,則忽略之
- 否則以 e 為據因拒絕 promise
- 如果 then 不是函式,以 x 為引數執行 promise
- 如果 x 不為物件或者函式,以 x 為引數執行 promise
如果一個 promise 被一個迴圈的 thenable 鏈中的物件解決,而 [[Resolve]](promise, thenable) 的遞迴性質又使得其被再次呼叫,根據上述的演算法將會陷入無限遞迴之中。演算法雖不強制要求,但也鼓勵施者檢測這樣的遞迴是否存在,若檢測到存在則以一個可識別的 TypeError 為據因來拒絕 promise。
參考上述規範,結合程式碼中的註釋,相信大家可以理解 resolvePromise()
的作用了。
測試:
test.js
let MyPromise = require('./MyPromise.js'); let promise = new MyPromise(function(resolve, reject) { setTimeout(function() { resolve(123); }, 1000); }); promise.then((value) => { console.log('value1', value); return new MyPromise((resolve, reject) => { resolve(456); }).then((value) => { return new MyPromise((resolve, reject) => { resolve(789); }) }); }, (reason) => { console.log('reason1', reason); }).then((value) => { console.log('value2', value); }, (reason) => { console.log('reason2', reason); }); 複製程式碼
列印結果:
value1 123value2 789
6. 讓 then()
方法的回撥函式總是非同步呼叫
官方 Promise
實現的回撥函式總是非同步呼叫的:
console.log('start'); let promise = new Promise((resolve, reject) => { console.log('step-'); resolve(123); }); promise.then((value) => { console.log('step--'); console.log('value', value); }); console.log('end'); 複製程式碼
列印結果:
startstep-endstep--value1 123
Promise屬於微任務,這裡我們為了方便用巨集任務 setTiemout
來代替實現非同步,具體關於巨集任務、微任務以及Event Loop可以參考我的另一篇文章 帶你徹底弄懂Event Loop 。
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) { let self = this; let promise2 = null; promise2 = new MyPromise((resolve, reject) => { if (self.state === PENDING) { self.onFulfilledCallbacks.push(() => { setTimeout(() => { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); }); self.onRejectedCallbacks.push(() => { setTimeout(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); }); } if (self.state === FULFILLED) { setTimeout(() => { try { let x = onFuifilled(self.value); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); } if (self.state === REJECTED) { setTimeout(() => { try { let x = onRejected(self.reason); self.resolvePromise(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }, 0); } }); return promise2; }; 複製程式碼
測試:
test.js
let MyPromise = require('./MyPromise.js'); console.log('start'); let promise = new MyPromise((resolve, reject) => { console.log('step-'); setTimeout(() => { resolve(123); }, 1000); }); promise.then((value) => { console.log('step--'); console.log('value', value); }); console.log('end'); 複製程式碼
列印結果:
startstep-endstep--value1 123
經過以上步驟,一個最基本的Promise就已經實現完了,下面我們會實現一些不在PromiseA+規範的擴充套件方法。
7. 實現 catch()
方法
then()
方法的 onFulfilled
和 onRejected
回撥函式都不是必傳項,如果不傳,那麼我們就無法接收 reject(reason)
中的錯誤,這時我們可以通過鏈式呼叫 catch()
方法用來接收錯誤。舉例:
let promise = new Promise((resolve, reject) => { reject('has error'); }); promise.then((value) => { console.log('value', value); }).catch((reason) => { console.log('reason', reason); }); 複製程式碼
列印結果:
reason has error
不僅如此, catch()
可以作為Promise鏈式呼叫的最後一步,前面Promise發生的錯誤會冒泡到最後一個 catch()
中,從而捕獲異常。舉例:
let promise = new Promise((resolve, reject) => { resolve(123); }); promise.then((value) => { console.log('value', value); return new Promise((resolve, reject) => { reject('has error1'); }); }).then((value) => { console.log('value', value); return new Promise((resolve, reject) => { reject('has error2'); }); }).catch((reason) => { console.log('reason', reason); }); 複製程式碼
列印結果:
reason has errorreason has error1
那麼 catch()
方法到底是如何實現的呢?
答案就是在Promise的實現中, onFulfilled
和 onRejected
函式是有預設值的:
MyPromise.js
MyPromise.prototype.then = function(onFuifilled, onRejected) { onFuifilled = typeof onFuifilled === 'function' ? onFuifilled : value => {return value;}; onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}; }; MyPromise.prototype.catch = function(onRejected) { return this.then(null, onRejected); }; 複製程式碼
可以看到, onRejected
的預設值是把錯誤 reason
通過 throw
丟擲去。由於我們對於同步程式碼的執行都是在 try...catch
中的,所以如果Promise發生了錯誤,如果沒傳 onRejected
,預設的函式會把錯誤 reason
丟擲,然後會被promise2捕捉到,作為 reject(reason)
決議。
catch()
實現就是呼叫 this.then(null, onRejected)
,由於 promise2
被 reject
,所以會執行 onRejected
回撥,於是就捕捉到了第一個promise的錯誤。
總結來說, then()
方法中不傳 onRejected
回撥, Promise
內部會預設幫你寫一個函式作為回撥,作用就是 throw
丟擲 reject
或者 try...catch
到的錯誤,然後錯誤 reason
會被 promise2
作為 reject(reason)
進行決議,於是會被下一個 then()
方法的 onRejected
回撥函式呼叫,而 catch
只是寫了一個特殊的 then(null, onRejected)
而已。
所以,我們在寫 Promise
的鏈式呼叫的時候,在 then()
中可以不傳 onRejected
回撥,只需要在鏈式呼叫的最末尾加一個 catch()
就可以了,這樣在該鏈條中的 Promise
發生的錯誤都會被最後的 catch
捕獲到。
舉例1:
let promise = new Promise((resolve, reject) => { reject(123); }); promise.then((value) => { // 注意,不會走這裡,因為第一個promise是被reject的 console.log('value1', value); return new Promise((resolve, reject) => { reject('has error1'); }); }).then((value) => { console.log('value2', value); return new Promise((resolve, reject) => { reject('has error2'); }); }, (reason) => { // 注意,這個then有onRejected回撥 console.log('reason2', reason); }).catch((reason) => { // 錯誤在上一個then就被捕獲了,所以不會走到這裡 console.log('reason3', reason); }); 複製程式碼
列印結果:
reason2 123
舉例2:
let promise = new Promise((resolve, reject) => { reject(123); }); promise.then((value) => { console.log('value1', value); return new Promise((resolve, reject) => { reject('has error1'); }); }).then((value) => { console.log('value2', value); return new Promise((resolve, reject) => { reject('has error2'); }); }).catch((reason) => { // 由於鏈條中的then都沒有onRejected回撥,所以會一直被冒泡到最後的catch這裡 console.log('reason3', reason); }); 複製程式碼
catch
和 then
一樣都是返回一個新的 Promise
。有的同學可能會有疑問,如果 catch
中的回撥執行也發生錯誤該怎麼辦呢,這個我們後續在Promise異常處理中再做討論。
列印結果:
reason3 123
8. 實現 finally
方法
finally
是某些庫對 Promise
實現的一個擴充套件方法,無論是 resolve
還是 reject
,都會走 finally
方法。
MyPromise.js
MyPromise.prototype.finally = function(fn) { return this.then(value => { fn(); return value; }, reason => { fn(); throw reason; }); }; 複製程式碼
9. 實現 done
方法
done
方法作為 Promise
鏈式呼叫的最後一步,用來向全域性丟擲沒有被 Promise
內部捕獲的錯誤,並且不再返回一個 Promise
。一般用來結束一個 Promise
鏈。
MyPromise.js
MyPromise.prototype.done = function() { this.catch(reason => { console.log('done', reason); throw reason; }); }; 複製程式碼
10. 實現 Promise.all
方法
Promise.all()
接收一個包含多個 Promise
的陣列,當所有 Promise
均為 fulfilled
狀態時,返回一個結果陣列,陣列中結果的順序和傳入的 Promise
順序一一對應。如果有一個 Promise
為 rejected
狀態,則整個 Promise.all
為 rejected
。
MyPromise.js
MyPromise.all = function(promiseArr) { return new MyPromise((resolve, reject) => { let result = []; promiseArr.forEach((promise, index) => { promise.then((value) => { result[index] = value; if (result.length === promiseArr.length) { resolve(result); } }, reject); }); }); }; 複製程式碼
test.js
let MyPromise = require('./MyPromise.js'); let promise1 = new MyPromise((resolve, reject) => { console.log('aaaa'); setTimeout(() => { resolve(1111); console.log(1111); }, 1000); }); let promise2 = new MyPromise((resolve, reject) => { console.log('bbbb'); setTimeout(() => { reject(2222); console.log(2222); }, 2000); }); let promise3 = new MyPromise((resolve, reject) => { console.log('cccc'); setTimeout(() => { resolve(3333); console.log(3333); }, 3000); }); Promise.all([promise1, promise2, promise3]).then((value) => { console.log('all value', value); }, (reason) => { console.log('all reason', reason); }) 複製程式碼
列印結果:
aaaabbbbcccc11112222all reason 22223333
11. 實現 Promise.reace
方法
Promise.race()
接收一個包含多個 Promise
的陣列,當有一個 Promise
為 fulfilled
狀態時,整個大的 Promise
為 onfulfilled
,並執行 onFulfilled
回撥函式。如果有一個 Promise
為 rejected
狀態,則整個 Promise.race
為 rejected
。
MyPromise.js
MyPromise.race = function(promiseArr) { return new MyPromise((resolve, reject) => { promiseArr.forEach(promise => { promise.then((value) => { resolve(value); }, reject); }); }); }; 複製程式碼
test.js
let MyPromise = require('./MyPromise.js'); let promise1 = new MyPromise((resolve, reject) => { console.log('aaaa'); setTimeout(() => { resolve(1111); console.log(1111); }, 1000); }); let promise2 = new MyPromise((resolve, reject) => { console.log('bbbb'); setTimeout(() => { reject(2222); console.log(2222); }, 2000); }); let promise3 = new MyPromise((resolve, reject) => { console.log('cccc'); setTimeout(() => { resolve(3333); console.log(3333); }, 3000); }); Promise.all([promise1, promise2, promise3]).then((value) => { console.log('all value', value); }, (reason) => { console.log('all reason', reason); }) 複製程式碼
列印結果:
aaaabbbbcccc1111all reason 111122223333
12. 實現 Promise.resolve
方法
Promise.resolve
用來生成一個 fulfilled
完成態的 Promise
,一般放在整個 Promise
鏈的開頭,用來開始一個 Promise
鏈。
MyPromise.js
MyPromise.resolve = function(value) { let promise; promise = new MyPromise((resolve, reject) => { this.prototype.resolvePromise(promise, value, resolve, reject); }); return promise; }; 複製程式碼
test.js
let MyPromise = require('./MyPromise.js'); MyPromise.resolve(1111).then((value) => { console.log('value1', value); return new MyPromise((resolve, reject) => { resolve(2222); }) }).then((value) => { console.log('value2', value); }) 複製程式碼
列印結果:
value1 1111value2 2222
由於傳入的 value
有可能是普通值,有可能是 thenable
,也有可能是另一個 Promise
,所以呼叫 resolvePromise
進行解析。
12. 實現 Promise.reject
方法
Promise.reject
用來生成一個 rejected
失敗態的 Promise
。
MyPromise.js
MyPromise.reject = function(reason) { return new MyPromise((resolve, reject) => { reject(reason); }); }; 複製程式碼
test.js
let MyPromise = require('./MyPromise.js'); MyPromise.reject(1111).then((value) => { console.log('value1', value); return new MyPromise((resolve, reject) => { resolve(2222); }) }).then((value) => { console.log('value2', value); }).catch(reason => { console.log('reason', reason); }); 複製程式碼
列印結果:
reason 1111
13. 實現 Promise.deferred
方法
Promise.deferred
可以用來延遲執行 resolve
和 reject
。
MyPromise.js
MyPromise.deferred = function() { let dfd = {}; dfd.promies = new MyPromise((resolve, reject) => { dfd.resolve = resolve; dfd.rfeject = reject; }); return dfd; }; 複製程式碼
這樣,你就可以在外部通過呼叫 dfd.resolve()
和 dfd.reject()
來決議該 Promise
。
13. 如何停止一個 Promise
鏈
假設這樣一個場景,我們有一個很長的 Promise
鏈式呼叫,這些 Promise
是依次依賴的關係,如果鏈條中的某個 Promise
出錯了,就不需要再向下執行了,預設情況下,我們是無法實現這個需求的,因為 Promise
無論是 then
還是 catch
都會返回一個 Promise
,都會繼續向下執行 then
或 catch
。舉例:
new Promise(function(resolve, reject) { resolve(1111) }).then(function(value) { // "ERROR!!!" }).catch() .then() .then() .catch() .then() 複製程式碼
有沒有辦法讓這個鏈式呼叫在ERROR!!!的後面就停掉,完全不去執行鏈式呼叫後面所有回撥函式呢?
我們自己封裝一個 Promise.stop
方法。
MyPromise.js
MyPromise.stop = function() { return new Promise(function() {}); }; 複製程式碼
stop
中返回一個永遠不執行 resolve
或者 reject
的 Promise
,那麼這個 Promise
永遠處於 pending
狀態,所以永遠也不會向下執行 then
或 catch
了。這樣我們就停止了一個 Promise
鏈。
new MyPromise(function(resolve, reject) { resolve(1111) }).then(function(value) { // "ERROR!!!" MyPromise.stop(); }).catch() .then() .then() .catch() .then() 複製程式碼
但是這樣會有一個缺點,就是鏈式呼叫後面的所有回撥函式都無法被垃圾回收器回收。
14. 如何解決 Promise
鏈上返回的最後一個 Promise
出現錯誤
看如下例子:
new Promise(function(resolve) { resolve(42) }).then(function(value) { a.b = 2; }); 複製程式碼
這裡a不存在,所以給a.b賦值是一個語法錯誤, onFulfilled
回撥函式是包在 try...catch
中執行的,錯誤會被 catch
到,但是由於後面沒有 then
或 catch
了,這個錯誤無法被處理,就會被 Promise
吃掉,沒有任何異常,這就是常說的 Promise有可能會吃掉錯誤 。
那麼我們怎麼處理這種情況呢?
方法一
就是我們前面已經實現過的 done()
。
new Promise(function(resolve) { resolve(42) }).then(function(value) { a.b = 2; }).done(); 複製程式碼
done()
方法相當於一個 catch
,但是卻不再返回 Promise
了,注意 done()
方法中不能出現語法錯誤,否則又無法捕獲了。
方法二
普通錯誤監聽 window
的 error
事件可以實現捕獲
window.addEventListener('error', error => { console.log(error); // 不會觸發 }); 複製程式碼
Promise沒有被 onRejected()
處理的錯誤需要監聽 unhandledrejection
事件
window.addEventListener('unhandledrejection', error => { console.log('unhandledrejection', error); // 可以觸發,而且還可以直接拿到 promise 物件 }); 複製程式碼
14. 單元測試
結束
相關單元測試以及完整程式碼可以到我的 github 檢視,如果對你有幫助的話,就來個star吧~

