前言
Promise大家一定都不陌生了,JavaScript異步流程從最初的Callback,到Promise,到Generator,再到目前使用最多的Async/Await(如果對於這些不熟悉的可以參考我另一篇文章《JavaScript異步編程》),這不僅僅是技術實現的發展,更是思想上對於如何控制異步的遞進。Promise作為後續方案的基礎,是重中之重,也是面試時候最常被問到的。
今天我們就一起從0到1實現一個基於A+規範的Promise,過程中也會對Promise的異常處理,以及是否可手動終止做一些討論,最後會對我們實現的Promise做單元測試。完整的代碼已經上傳到github,想直接看代碼的可以點這裏。
雖然已經有很多帶你實現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 123
value2 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 123
reason2 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 123
value2 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 123
value2 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');
打印結果:
start
step-
end
step--
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');
打印結果:
start
step-
end
step--
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);
});
打印結果:
value 123
reason 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);
})
打印結果:
aaaa
bbbb
cccc
1111
2222
all reason 2222
3333
11. 實現Promise.race方法
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.race([promise1, promise2, promise3]).then((value) => {
console.log('all value', value);
}, (reason) => {
console.log('all reason', reason);
})
打印結果:
aaaa
bbbb
cccc
1111
all reason 1111
2222
3333
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 1111
value2 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吧~
歡迎關注我的公眾號
參考文檔
PromiseA+規範