axios 的錯誤處理

godruoyi發表於2021-01-21

Axios 是目前使用最為廣泛的 http 請求工具包,在進行錯誤處理時,基於框架提供的攔截器,我們可以快速的實現錯誤處理。

本文基於 一種 Laravel 異常上下文解決方案 的評論討論,也歡迎大家訪問我的 部落格

axios.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    // 如4xx/5xx等基本錯誤的處理
    alert('全域性錯誤處理')

    return Promise.reject(error); 
});

但 axios 提供的 response 攔截器是全域性的,若我們想對某個具體請求進行錯誤處理時,情況就稍微有點複雜了。

如有一個投票介面,由於後端限制了投票次數,當投票超限時我們需要單獨處理;返回的響應如下:

HTTP/1.0 429 Too Many Requests

{"error_code":4291011,"message":"今日投票次數超限"}

可能對於部分前端同學來說,處理方式是直接在 response 攔截器加上相應的條件判斷就好了:

axios.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    if (error.response.status == 429) {
        if (error.response.data.error_code == 4291011) {
            // 單獨處理投票錯誤
        } else if (error.response.data.error_code == 4291011) {
            // 作品票數異常,需先透過滑動驗證碼
        }
    }

    // 處理其他 如4xx/5xx等基本錯誤的處理
    return Promise.reject(error); 
});

但是這樣做卻存在很多問題

  • 隨著各種錯誤碼的增多,攔截器需要處理的情況越來越多,最終充滿著大量的 if-else
  • 具體的錯誤碼應該和具體發起請求的程式碼放在一起,一來方便檢視,二來好擴充套件及定位

所以我們可以把錯誤處理邏輯移到呼叫程式碼處,如下:

axios.post('/vote/1').then(function (response) {
    // success
}).catch(function (error) {
    let code = error.response.data.error_code
    if (code == 4291011) {
        alert('投票超限')
    } elseif (code == 4030001) {
        alert('作品票數異常,需先透過滑動驗證碼')
    }
});

但這樣又存在一個問題,由於 axios 攔截器的程式碼會比 catch 先執行,所以當執行到 catch 時,實際上 response 攔截器的程式碼已全部執行完成,所以會先後彈出「全域性錯誤處理」-> 「投票超限」。

這顯然不是我們想要的,我們希望當我們單獨處理錯誤後,先執行具體的業務錯誤處理,最後在執行全域性的錯誤處理。

如下:

  • 針對這種沒有 catch 的情況,當請求錯誤後,我們希望由全域性錯誤進行處理。
axios.post('/vote/1').then(function (response) {
    // success
})

這預設是可行的,不需要做額外的準備工作。

  • 當我們使用 catch 後,我們希望應該先處理自定義的錯誤,最後在處理全域性錯誤。
axios.post('/vote/1').then(function (response) {
    // success
}).catch(error => {
    // custom error 
})

然而目前這樣是行不通的,看了 axios request 方法原始碼後得知,框架在發起請求時,並沒有給我提供相應的鉤子;所以在 Promise 執行到 catch 時,攔截器裡的程式碼一定已經執行過了。

我們只能依賴 axios 提供的 config 來完成這個特性,如下所示:

axios.interceptors.response.use(function (response) {
    return response;
}, function (error) {
    error.globalErrorProcess = function () {
        switch (this.response.status) {
            case 401: // 處理基本 401 錯誤
                break;
            case 404: // 處理基本 404 錯誤
                break;
            case 403: // 處理基本 403 錯誤
                break;
                      // 處理其他4xx/5xx等基本錯誤的處理
        }

        return Promise.reject(this);
    };

    if(error.config.hasOwnProperty('catch') && error.config.catch == true) {
        return Promise.reject(error);
    }

    return error.globalErrorProcess()
});

我們定義一個全域性的錯誤處理器,並把他賦給 error 物件的 globalErrorProcess 方法。接著判斷當前請求 config 是否啟用 catch,若啟用,預設不進行任何錯誤處理,交由呼叫方自行負責;否則用全域性錯誤處理。

在使用時,若需要自定義捕獲錯誤,可顯示傳遞一個 config,相應請求方法的 API 如下:

  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.options(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])
axios.post('https://api.github.com/xxx', null, {catch: true}).then(function (response) {
    console.log(response);
}).catch(function (error) {
    let code = error.response.data.error_code

    if (code == 4291011) {
        // 今日投票次數太多,顯示關注公眾號二維碼
    } else if (code == 4031011) {
        // 不允許的投票時間段,
    } else if (code == 4291012) {
        // 作品票數異常,需先透過滑動驗證碼
    }

    return error.globalErrorProcesser()
});

最後別忘了顯示的呼叫全域性錯誤處理,否則是不會懶覺到其他異常處理的。

如果你有更好的方案,歡迎留言一起探討。

參考 & 討論

本作品採用《CC 協議》,轉載必須註明作者和本文連結
二愣的閒談雜魚

相關文章