0%

Promise (IV)- Error handling

本文開始

在一個 Promise chaining 中,如果某一個階段的 promise 呼叫了 reject ,則該 chaining 的處理位置會直接從呼叫 reject 的 promise 那邊,跳到離它最近的的 rejection handler。

上個範例

1
2
3
4
5
6
7
new Promise((resolve, reject) => {
reject('error'); // 呼叫 reject
})
.then(res => console.log('fulfilled' + res))
.then(res => console.log('fulfilled' + res))
.then(res => console.log('fulfilled' + res))
.catch(err => console.log('reject ' + err)) // 它被觸發,並印出 reject error

由上面的範例就可以看得出來 catch 不需要是接在呼叫 reject 的那個 Promise 後面的第一個鏈結對象,即便中間跨過很多的.then ,但仍然會直接呼叫到離發生地點最近的 catch 。

Implicit try…catch

在 promise executor(new Promise 在寫的) 和 handler function (傳入 .then 的 function) 都會有一個看不見的 try…catch 包裹在它們外圍。
如果有一個 例外情況(exception) 發生了,則它會會被接起來,並自動呼叫 reject 。

先來個範例

1
2
3
4
new Promise((resolve, reject) => {
throw new Error("Whooops!")
})
.catch(alert); // Error: Whooops!

上面的程式碼可以寫成下面這樣,有一樣的效果

1
2
3
4
new Promise((resolve, reject) => {
reject(new Error("Whooops!"))
})
.catch(alert) // Error: Whooops!

從上面的範例可以看出來,在 executor 周圍的隱形的 try…catch 會自動的把 error 接起來,並將它當為 reject 的值。

上面的範例,都是寫在 executor 裡面拋出錯誤,下面也來寫個由 handler function 拋出錯誤的範例

1
2
3
4
5
6
7
new Promise((resolve, reject) => {
resolve("ok");
})
.then(res => {
throw new Error("Whooops!!") // 丟出錯誤,並自動被包裹在 reject 裡面
})
.catch(alert); // 被觸發,並顯示 Error: Whooops!!

除了上面都是拋出錯誤的範例,下面這邊是程式碼撰寫上發生的錯誤,呼叫沒有被定義的 function

1
2
3
4
5
new Promise((resolve, reject) => {
resolve('123')
})
.then(() => nonDefinedFnc())
.catch(alert); // ReferenceError: nonDefinedFnc is not defined

Rethrowing

假設一個 Promise chaining 執行的流程,因為遇到 throw 而進入到某個 .catch
此時,這個 chaining 執行的流程將會取決於這個 .catch 不同的操作,而執行後續不一樣的路線,

假設
.catch 又再 throw 一次,則這個 stream 又會跑到離這個 .catch 最近的下一個 error handler 。
但如果我們在 .catch 執行過後,讓他正常的結束,則它會繼續執行下一個 .then

下面的範例是讓 .catch 正常的結束,往後執行其它的 .then

1
2
3
4
5
new Promise((resolve, reject) => {
reject('error');
})
.catch(() => alert("The error is handled, continue normally"))
.then(() => alert("Next Success handler runs"))

下面的範例,就是在一個 .catch 裡面再用 throw ,讓 stream 進入到下一個最近的 error handler 中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
new Promise((resolve, reject) => {
throw new Error("Whooops!!");
})
.catch(err => { // step 1
if(err instanceof URIError) {
// ...
}
else {
alert("Can't handle such error");

throw err; // 將 error 丟出來
}
})
.then(() => {})
.catch(error => { // step 2 - 會接住由 step 1 丟出來的錯誤

alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way

})

上面的範例就會先執行 step 1 ,因為在 step 1 throw 東西出來,所以再進到 step 2 裡面。

Unhandled rejections

如果,Promise 執行的 stream 裡面,有拋出錯誤,但是,沒有任何 error handler 去承接這個錯誤的話,會發生什麼事呢?

像下面這個範例

1
2
3
4
5
6
7
new Promise((resolve, reject) => {
noSuchFnc(); // Error 發生 (no such defined)
})
.then(() => {
// 成功的 Promise 會執行的地方
});
// 後續沒有 .catch 來承接錯誤

上面的範例,當 promise 呼叫了 reject,照理來接下來就要跳到最近的 error handler 來承接,但是,卻沒有這個 error handler,這個時候,error 會 “卡” 住,因為沒有人可以接續後面的處理。

如果,有錯誤發生但沒有被 try…catch 給接起來的話,JS 會卡住並丟出一個訊息到 console 裡,那沒有被接住的 promise rejection 也會發生類似的事情。

JS 其實會追蹤這種沒有被接住的錯誤,並在追蹤到這種狀況發生的時候,丟出一個全域性的錯誤。

並且可以利用 unhandledRejection 這個 function 來監聽這種錯誤的發生,
用法如以下範例

1
2
3
4
5
6
7
8
window.addEventListener('unhandledRejection', function (event) {
// the event object has two special properties
console.log(event.promise); // [object Promise] - error
console.log(event.reason); // Error: Error!! - the unhandled error
});
new Promise(function () {
noSuchFunction();
}); // no catch to handle the error

以上的範例,加在 unhandledRejection 的 callback function 會傳入一個 event,而這個 event 就會包含觸發這個事件的 error 內容。

而這種沒有錯誤機制去承接錯誤的這種狀況,通常最好的應對方式就是通知使用者發生這種問題了,並且回報此問題給 server。

Conclusion

用 Promise 來做錯誤處理是蠻方便的,因為,我們可以直接將 .catch 接在 Promise chaining 中,它就會接住由前面的發送出來的錯誤 (不管是 reject 傳出來的, throw 出來的 或者 undefined error 都會被接住)。~~

最後,我們會需要使用 unhandledRejection 來替我們處理當有一些沒有做錯誤承接的錯誤發生的時候的處理或通知。

Reference

  1. https://javascript.info/promise-error-handling