無痛處理async-await的異常,告別一個請求一個try-catch----Axios示例

語言: CN / TW / HK

async-await的痛點

async-await搭檔雖然也是返回Promise物件,但無法做到.then.catch那樣拿到Promise中reject的結果以及其中丟擲的Error。

換句話說,我們用async-await來寫非同步請求很多時候會這樣: ts async function getUserInfo() { try { const res = await fetchUserInfo(id); } catch(err) { // 如果你用了axios攔截器,這裡會是攔截器reject的結果 // 只有通過try-catch你才能拿到這個錯誤 } // 其他的業務程式碼 } 很多時候我們在axios攔截器的內部統一處理了錯誤,並使用一些UI框架的訊息彈出功能,將錯誤顯示給使用者,這個邏輯是沒問題的,但會有一些需要特殊處理的邏輯。例如載入動畫的控制: ts async function getTableData() { this.loading = true; try { const res = await fetchTable({ page, size }); } catch(err) { // 如果你用了axios攔截器,這裡會是攔截器reject的結果 // 只用通過try-catch你才能拿到這個錯誤 } finally { this.loading = false; } } 這是非常常見的業務邏輯,不管是報錯還是正常,你需要在介面給你迴應後將loading效果給去掉。因此這種情況是必須使用try-catch的,另外還有一些諸如: - 請求異常後回到上一頁面。 - 請求異常後清空某些資料。 - 請求異常後再次發起請求。 太多了,這類情況在業務中很常見,這裡只是舉了幾個例子。那麼對於這些情況,如果使用async-await的請求風格,我們就必須每個請求都包一個try-catch,這是很麻煩的。

使用Proxy代理非同步函式

我使用的axios,下面用axios來做為例子。

利用Proxy的apply陷阱攔截函式執行,並在其中統一try-catch: ```ts // 在utils檔案中我們編寫了一個函式。使用代理模式的思想,我們在其中統一處理了異常 export function createErrorProxy(fn: (...args: any[])=> Promise) { // 你可以在這裡判斷當前環境是否支援Proxy,不支援的話就把回撥原封不動返回,不過這樣肯定會導致一些意外 const proxyFn = new Proxy(fn, { async apply(target, context, args) { try { return await Reflect.apply(target, context, args); } catch(err) { return err; } } });

return proxyFn; } 接下來,需要做的就是重寫axios的幾個例項方法:ts import axios from "axios"; import { createErrorProxy } from "@/utils";

const axiosInstance = axios.create({/ config /}); // 攔截器之類的程式碼 // ... // 使用Proxy重寫post等你用得上的請求方法 ["post", "get", "delete", "put"].forEach(type=> { axiosInstance[type] = createErrorProxy(axiosInstance[type]); });

export default axiosInstance; 到這裡其實就搞定了,下面是非常常見的api封裝:ts // 這塊就是剛才我們處理過的axios import request from "@/request"; // api檔案下的使用者資訊請求模組 export function fetchUserInfo(id: string): Promise { return request.get(/system/user/${id}); } // .... ``` 匯入使用時就不再需要try-catch包裹了。

總結

相較於.then.catch,我更喜歡使用async-await搭檔,這對基於Generator的關鍵字讓非同步程式碼能夠以同步的編碼方式書寫出來,搭配解構與Error-First是我最喜歡的請求風格: ts async function getTableData() { const [error, userInfo] = await fetchUserInfo(id); if (error) { // 錯誤的處理 return; } // 正常情況 userModule.$patch({ userInfo }); } 你要說它比.then.catch少寫了程式碼嗎,似乎沒有,這就看個人喜好了。

回到正題,經過代理後的axios請求已經統一處理了異常,後續的所有請求都不用再一次一次的重複寫try-catch,我們可以放心的在await後面做一些事情。

這篇文章很短,但希望能給大家一點幫助,如果有錯誤或者建議歡迎各位大佬在評論區批評指正。