不是吧?async-await 異常捕獲你還在用 try-catch~
寫在前面
不知道大家項目裏面是怎麼處理 async/await 的異常,我斗膽在我們項目裏翻了一下,發現大量使用 try-catch 來處理 async/await 異常。
首先說明一下, try-catch 處理並沒有什麼問題,我只是覺得這麼寫代碼會有點亂,感覺代碼邏輯像是斷層了一樣,不易理解;
其次是代碼冗餘問題,單個 try-catch 就佔了好幾行代碼,如果每個請求的地方都添加 try-catch,就會顯得代碼很臃腫。 而對於這種大量相同的冗餘代碼,完全可以用一種通用的函數來替代。
async/await 是在 ES2017 中引入的,目的是爲了讓異步操作更加直觀、方便,同時也解決了 Promise 的回調地獄問題。想必這些概念大家都已經瞭解了,那麼我們爲什麼要捕獲 async/await 的異常呢?它們是在什麼時候發生異常呢? 帶着問題我們一起看一下本文。
什麼時候會請求異常
我們都知道 await 後面一般都是異步請求,異步請求發生異常的原因大致有以下幾種:
-
網絡問題導致,網絡斷開連接,請求不到;
-
網絡慢導致異步請求超時。
什麼情況下需要處理請求異常
一旦有以上情況出現,這個異步請求就會產生異常,而 JavaScript 又是一個單線程語言,代碼報錯後就會導致後面的代碼無法繼續執行,所以此時就需要添加 try-catch 來捕獲異步請求的異常,使得代碼可以繼續向後執行。
但有必要爲所有的異步請求都加 try-catch 嗎?
我研究了下我們項目的代碼,異步請求加了 try-catch 處理的,有以下幾種情況:
多個異步請求串行
try {
// 獲取列表list
const list = await getList(params)
// 獲取單個詳情
const info = await getListById(list[0]?.id)
} catch {}
前一個異步請求的返回結果,會作爲後一個異步請求的請求參數使用,所以一旦前一個請求異常,後面的請求肯定會異常,所以需要添加 try-catch 處理。
處理異步請求的 loading 狀態
loading.value = true
try {
// 獲取列表list
const list = await getList(params)
} finally {
loading.value = false
}
一般我們處理異步請求前,會爲其添加 loading 狀態,而一旦請求異常出現時,如果不加 try-catch 時就會導致頁面一直處於 loading 狀態。所以需要在finally
中將 loading 狀態置爲 false,catch中處理時需要外部再處理一次
。
那麼,我們如何優雅的處理異步請求中的 try-catch 呢?
處理方法
使用 Promise 處理
首先需要明確一點:正常情況下,await 命令後面是一個 Promise 對象 [1]。所以它本身就可以使用.catch
來捕獲異常,那麼像上面第二種只是處理 loading 狀態的操作,完全可以在.catch
進行處理,然後用if
判斷來控制提前退出,沒必要寫 try-catch 這種冗餘代碼。
loading.value = true
let res = await getList().catch(() => (loading.value = false))
if (!res) return
// 請求成功後正常操作
await-to-js 處理函數
簡單的異步請求我們可以使用上面這種方法,但遇到多個異步操作時,就需要藉助我們今天要說的 await-to-js[2] 這個庫,它的介紹很簡單:無需 try-catch 即可輕鬆處理錯誤。
而且源碼 [3] 賊簡單,就 23 行代碼,我們一起來看看。
/**
* @param { Promise } promise
* @param { Object= } errorExt - Additional Information you can pass to the err object
* @return { Promise }
*/
export function to<T, U = Error>(
promise: Promise<T>,
errorExt?: object,
): Promise<[U, undefined] | [null, T]> {
return (
promise.then <
[null, T] >
((data: T) => [null, data]).catch <
[U, undefined] >
((err: U) => {
if (errorExt) {
const parsedError = Object.assign({}, err, errorExt);
return [parsedError, undefined];
}
return [err, undefined];
})
);
}
export default to;
大致流程如下: 函數to
接受參數Promise
和errorExt
,如果這個 Promise 成功時返回[null, data]
,如果異常時會判斷是否帶有errorExt
參數(代表傳遞給 err 對象的附加信息),如果有時會與 catch 捕獲的 err 合併返回,如果沒有時返回[err, undefined]
。
很簡單的邏輯是不是,接着我們看下它的用法:
- 安裝
# use npm
npm i await-to-js --save
# use yarn
yarn add await-to-js
- 使用
首先引入to
函數,可以看到包很小,只有 370b,gzip 壓縮後只有 242b,所以放心引入,別擔心什麼包大小問題。
我們通過to
來改寫一下上面第一種問題:
import to from 'await-to-js';
// 獲取列表list
const [err, data] = await to(getList(params));
if (err) return;
// 獲取單個詳情
const info = await to(getListById(list[0]?.id));
通過to
函數改造後,如果返回第一個參數不爲空時,說明該請求報錯,就可以提前 return 出去,如果不存在第一個參數時,則異步請求成功。
總結
本文通過研究 async/await 的異常捕獲,發現了兩種常見的異步請求異常捕獲,並提出了兩種簡單的解決方法。通過這兩種方法,就可以丟掉冗餘的 try-catch,然後你就會發現沒了 try-catch 的代碼看起來都是順眼的。
很多小夥伴可能也會遇到這個問題:儘管你提出瞭解決方案,但依舊會有項目組成員不用。你要這麼想就錯了,自己通過研究、查資料,最終學到了東西就足夠了,管別人幹嘛!沒必要啊。
參考資料
[1]
https://es6.ruanyifeng.com/#docs/async#await-%E5%91%BD%E4%BB%A4
[2]
https://github.com/scopsy/await-to-js
[3]
https://github.com/scopsy/await-to-js/blob/master/src/await-to-js.ts
關於本文
作者:翔子丶
https://juejin.cn/post/7224391827654180922
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/0FV8uHs6Stsz0m-JTG07wA