前言 在上一篇 有討論到如何去寫 async/await 的 try-catch 比較好 那這篇會注重在另一種在最外層不需要 try-catch 的寫法上
那因為用 try-catch 和不用 try-catch 的場景比較不一樣 (最外層) 最後面會去比較這兩種寫法的優劣
寫法一 先來複習之前提到過的寫法
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 35 36 function test1 ( ) { return new Promise ((res, rej ) => { setTimeout(() => { rej("test1 have error." ) }, 1000 ) }) } function test2 ( ) { return new Promise ((res, rej ) => { setTimeout(() => { rej("test2 have error." ) }, 1000 ) }) } function test3 ( ) { return new Promise ((res, rej ) => { setTimeout(() => { rej("test3 have error." ) }, 1000 ) }) } async function main ( ) { try { let result result = await test1(); console .log(result); result = await test2(); console .log(result); result = await test3(); console .log(result); } catch (error) { console .log("get error" ); } } main()
可以看到透過用 Promise 裡面 reject 的方法, 可以客製每一個回傳的錯誤訊息 但 … 如果我不想讓程式執行到 reject 的時候跳到 catch 的地方 (第 33 行), 該怎麼做?
寫法二 這邊程式邏輯是 test1 執行完, 就算有錯誤, 我還是依舊要執行 並且把 test1 的錯誤帶到 test2 去執行
新的寫法透過解構 Array 的方式可以達成此目的
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 35 36 37 38 39 40 41 42 43 44 45 46 47 async function to (promise ) { return promise.then(result => [null , result]).catch((error ) => [error, null ]) } function test1 ( ) { return new Promise ((res, rej ) => { setTimeout(() => { rej("test1 have error." ) }, 1000 ) }) } function test2 (data ) { return new Promise ((res, rej ) => { setTimeout(() => { console .log("test2 handle data: " + data); rej("test2 have error." ) }, 1000 ) }) } function handleTest1ResultIsNull (error ) { console .log("handleTest1ResultIsNull's error message " + error); return "someConditionalValue" } async function main ( ) { let error, result [error, result] = await to(test1()); console .log("result: " + result); if (error) { console .log("get error-1" ); console .log(error); } if (!result) { result = handleTest1ResultIsNull(error); } [error, result] = await to(test2(result)); console .log("result: " + result); if (error) { console .log("get error-2" ); console .log(error); return ; } } main()
注意到段程式碼 透過傳進去一個 promise 並使用 then 去取得回傳值 當 catch 發生得時候, 透過 return 的方法, 讓此 promise 不會直接噴出錯誤, 而是正常回傳值 1 2 3 async function to (promise ) { return promise.then(result => [null , result]).catch((error ) => [error, null ]) }
然後透過解構 Array 的方式可以取得回傳值 這樣即使 test1 的 promise 是丟出一個錯誤, 透過此 warpper function 就可以達成就算有錯誤, 程式還是依舊繼續執行下去
1 let [result, result] = await to(test1());
但這裡帶來一個問題, 如果程式邏輯是 test1 正確的時候回傳 A 值, 錯誤的時候給一個 default 值 這樣其實不用特別寫一個 wrapper function, 只要稍微更改 test1 裡面的邏輯即可
寫法三 這裡可以看到 test1 裡面改成, 當某一個 error 出現時 特別去處理此 error, 然後在用 resolve 讓此 promise 正常回傳而不要丟出 error
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 35 36 37 38 39 40 function test1 ( ) { return new Promise ((res, rej ) => { let error = "test1 have error" ; setTimeout(() => { if (error) { res(handleTest1ResultIsNull(error)) } else { res("success" ) } }, 1000 ) }) } function test2 (data ) { return new Promise ((res, rej ) => { setTimeout(() => { console .log("test2 handle data: " + data); rej("test2 have error." ) }, 1000 ) }) } function handleTest1ResultIsNull (error ) { console .log("handleTest1ResultIsNull's error message" + error); return "someConditionalValue" } async function main ( ) { try { let result result = await test1(); console .log("result: " + result); result = await test2(result); console .log("result: " + result); } catch (error) { console .log("get error" ); console .log(error); } } main()
比較 開始來比較一下兩種寫法的優劣
Wrapper function 透過此 wrapper function 是可以方便程式撰寫的時候判斷方法 可以清楚地去判斷此 error 要 log 什麼, 要不要停止執行, 或是要繼續往下都是非常彈性的 但缺點就是, 你會寫一堆 if (error)
判斷 這在這種寫法上是無可避免的
1 2 3 async function to (promise ) { return promise.then(result => [null , result]).catch((error ) => [error, null ]) }
另外有另一種寫法也可以有一樣的效果 就是把 wrapper function 直接寫在 await 後面一樣可以達到效果1 let [error, result] = await test1().then(result => [null , result]).catch(error => [error, null ]);
try-catch block 透過 try-catch block 可以輕鬆直接在 catch 的時候去統一處理 error 但前提是你每一個 promise 裡面的 error 要事先處理好, 而不是交由最外層的 try-catch 去處理 只是使用者這種方法, 如果 promise 裡面有 error, 但還想要繼續執行 就必須透過 resolve 的方式去更改程式邏輯, 這點在這也是無可避免的
1 2 3 4 5 try { await test1() } catch (error) { console .log(error) }
總結 兩種寫法應用場景其實不太一樣 如果邏輯之間是第一個成功, 第二個才能繼續這種, 就很適合使用 try-catch block 因為你前面錯誤發生, 就直接讓跳出去, 也不需要繼續執行了
但如果是不管第一個是否成功, 第二個都要繼續執行 (根據第一個執行的結果去處理) 就適合用此文提到的 wrapper function
不過要注意一下商業邏輯的部分, 以剛剛的寫法三 的例子 是因為 function 回傳值就只有兩種, 所以才可以透過寫法三 去修改, 就又變成 try-catch 的形式 只是這種改底層的方式, 如果此 function 在其他地方邏輯是, 此 function 成功後才能繼續往下跑其他 function 這樣有可能會讓其他地方邏輯爆掉, 請特別注意這件事
但如果商業邏輯是不管第一個是否成功, 第二個都要執行這種 (不根據第一個執行的結果去處理) 其實在寫程式設計上, 這兩種邏輯理論上是可以被拆開的 因為這兩個是毫無相關性的, 就不用硬寫在同一個地方
References
How to write async await without try-catch blocks in Javascript