不是吧?async-await 異常捕獲你還在用 try-catch~

寫在前面

不知道大家項目裏面是怎麼處理 async/await 的異常,我斗膽在我們項目裏翻了一下,發現大量使用 try-catch 來處理 async/await 異常。

首先說明一下, try-catch 處理並沒有什麼問題,我只是覺得這麼寫代碼會有點亂,感覺代碼邏輯像是斷層了一樣,不易理解;

其次是代碼冗餘問題,單個 try-catch 就佔了好幾行代碼,如果每個請求的地方都添加 try-catch,就會顯得代碼很臃腫。 而對於這種大量相同的冗餘代碼,完全可以用一種通用的函數來替代。

async/await 是在 ES2017 中引入的,目的是爲了讓異步操作更加直觀、方便,同時也解決了 Promise 的回調地獄問題。想必這些概念大家都已經瞭解了,那麼我們爲什麼要捕獲 async/await 的異常呢?它們是在什麼時候發生異常呢? 帶着問題我們一起看一下本文。

什麼時候會請求異常

我們都知道 await 後面一般都是異步請求,異步請求發生異常的原因大致有以下幾種:

  1. 網絡問題導致,網絡斷開連接,請求不到;

  2. 網絡慢導致異步請求超時。

什麼情況下需要處理請求異常

一旦有以上情況出現,這個異步請求就會產生異常,而 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接受參數PromiseerrorExt,如果這個 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,所以放心引入,別擔心什麼包大小問題。

await-to-js-size.png

我們通過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