上一篇主要是介紹如何使用
這篇會介紹該如何去在每一種使用方式之中去做 Error Handling
callback
相信各位有在使用別人第三方套件或是 Node.js 原生的 Library 都會發現一件事情
那就是 callback 第一個參數都會是 error
雖然這看似是一個不成文的規定,但仔細想想把 error 放在第一個是非常合理的
假設當 callback 參數回傳越來越多的時候,總不可能把 error 放在最後一個去處理
因為你會始終不知道哪一個會是 error (就算寫註解也會讀到瘋掉)
試想一下這幾段 code 就可以理解了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function test(cb) { cb(successful_data_1, successful_data_2) } test((successful_data_1, successful_data_2) => { })
function test(cb) { cb(error) } test((error) => { })
|
當遇到上面的狀況就會變得非常難判斷,但如果我整體改寫成這樣就會變得輕而易舉
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function test(cb) { cb(null, successful_data_1, successful_data_2) } test((error, successful_data_1, successful_data_2) => { if (error != null) { } })
function test(cb) { cb(error) } test((error, successful_data_1, successful_data_2) => { if (error != null) { } })
|
當然有人會說
『啊我就把所有參數丟到第一個當 Object 全部存起來,第二個就放 Error 也是一種方式啊』
這樣講的話當然沒錯,但如果把所有東西都放在第一個 Object 裡面
這樣參數就會有分類,使用問題也只是會徒增而已
再加上這算是一種共識了,所以跟潛規則走會比較方便一點
promise
Promise 處理 error 的方式就比較特別了,我們先來看看一般 promise 出錯的時候是怎麼抓取的
1 2 3 4 5 6
| function test() { return new Promise((res, rej) => { rej("this is error"); }) } test().then((data) => {console.log("Get " + data)}).catch((error) => {console.log("handle! " + error)});
|
上面為一般 promise 用 rej 的方式,外面用 catch 去抓住這個錯誤
但凡事要考慮例外,萬一有一個 error 是你沒辦法 rej 到的話,那該要怎麼抓取?
1 2 3 4 5 6
| function test() { return new Promise((res, rej) => { oqiwje() }) } test().then((data) => {console.log("Get " + data)}).catch((error) => {console.log("handle! " + error)});
|
會發現當在 Promise 裡面出錯的話,外面的 catch 也是能抓到的
其原因是因為 Promise 是有被一層內部的 try-catch 給包住
且在內部的 catch 那一邊套用了預設地 rej function
所以外面才抓得到
那如果放在 Promise 外面的話呢??
1 2 3 4 5 6
| function test() { oqiwje() return new Promise((res, rej) => { }) } test().then((data) => {console.log("Get " + data)}).catch((error) => {console.log("handle! " + error)});
|
咦!? 竟然抓不到,error 直接噴出來!
但這也不意外,因為出了 Promise 到了外面
那就是要透過自己去寫 try-catch 才能抓取到這個錯誤
1 2 3 4 5 6 7 8 9 10
| function test() { oqiwje() return new Promise((res, rej) => { }) } try { test().then((data) => {console.log("Get " + data)}).catch((error) => {console.log("handle! " + error)}); } catch (error) { console.log("handled by outer try-catch"); }
|
還有一種 Handle 方式是寫在內層 function 裡面
1 2 3 4 5 6
| function test() { return new Promise((res, rej) => { oqiwje() }).catch(error => "handle by inner function") } test().then((data) => {console.log("Get " + data)}).catch((error) => {console.log("handle! " + error)});
|
那因為在 inner function 裡面被抓取到,並且回傳
還記得 promise chain 中,如果 return 的話是會到下一個 then 去的
所以這邊會被外面的 then 給抓到,而不是 catch,這邊要注意
Promise 的 Error Handling 只要能確保能執行到 rej 就沒什麼問題了
然而在 Promise 之前用 try-catch 包起來或程式都丟到 Promise 裡面等他報錯丟出來也可以處理到
async/await
await catch error 的方式可以想成一般 try-catch 的方式
1 2 3 4 5 6 7 8 9 10 11 12 13
| function test() { return new Promise((res, rej) => { rej("QQQ"); }); } async function main() { try { let result = await test() } catch (error) { console.log("Handled by main") } } main()
|
而要特別注意的是,如果把 catch 寫在外面的 await 那裡的話
會造成程式不會往最外層的 catch 前進
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function test() { return new Promise((res, rej) => { rej("QQQ"); }); } async function main() { try { let result = await test().catch(() => {console.log("Handled by await")}) console.log("Still going") } catch (error) { console.log("Handled by main") } } main()
|
但如果透過在 catch function 裡面把 error 再次 throw 出來的話,是可以成功 throw 出來
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function test() { return new Promise((res, rej) => { rej("QQQ"); }); } async function main() { try { let result = await test().catch(() => { console.log("Handled by await") throw new Error("QQQQ") }) console.log("Still going") } catch (error) { console.log("Handled by main") } } main()
|
千萬要注意 return 和 throw 的方式會帶來不一樣的結果
使用 return 就跟 Promise 的 reject 的狀態下 return 是一樣的
他會回傳到下一個 then 裡面 (也就是 resolved 的狀態)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function test() { return new Promise((res, rej) => { rej("QQQ"); }); } async function main() { try { let result = await test().catch(() => { console.log("Handled by await") return new Error("QQQQ") }) console.log(result); console.log("Still going") } catch (error) { console.log("Handled by main") } } main()
|
而個人比較不建議的寫法是在 await 那一層做 Error Handling
而是盡量再底層那裡做 throw error 到最外面的 try-catch 去接
原因是這跟 Design Pattern 有關係
最外層的 main 可以想像是 Controller,而 test 可以想像成 Facade
在裡面得程式才是真正的商業邏輯
從下面程式來解讀的話,回家主要目的是要做功課
那做功課一定會有流程,像是先吃飯,洗澡,最後在讀書這樣的順序
但要怎麼吃飯洗澡讀書是要寫在每一個該做的項目的最裡面,而不會寫在順序那一層
這樣程式撰寫上會比較乾淨一點
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| function eatFirst() { return new Promise((res, rej) => { setTimeout(() => { res("Error"); }, 1000); }); } function getBook() { return new Promise((res, rej) => { setTimeout(() => { res("Error"); }, 1000); }); } function writeIt() { return new Promise((res, rej) => { setTimeout(() => { rej("Books are ate by dogs!!!"); }, 1000); }); } async function doHomeWork() { await eatFirst() await getBook() await writeIt() } async function main() { try { let result = await doHomeWork(); } catch (error) { console.log("Handled by main") } } main();
|
後記
這次主要介紹 Error Handling 的方式
也加了一些個人建議撰寫的方法,如果有其他想法歡迎大家來討論!