Axios 如何實現請求重試?

下面阿寶哥將介紹如何使用 Axios 提供的攔截器或適配器來實現請求重試的功能,如果你對 Axios 的攔截器或適配器還不熟悉的話,建議先閱讀 77.9K 的 Axios 項目有哪些值得借鑑的地方 這篇文章。接下來,我們先來介紹如何使用攔截器實現請求重試的方案。

一、攔截器實現請求重試的方案

Axios 是一個基於 Promise 的 HTTP 客戶端,而 HTTP 協議是基於請求和響應:

所以 Axios 提供了 請求攔截器和響應攔截器 來分別處理請求和響應,它們的作用如下:

在 Axios 中設置攔截器很簡單,通過 axios.interceptors.request 和 axios.interceptors.response 對象提供的 use 方法,就可以分別設置請求攔截器和響應攔截器:

export interface AxiosInstance {
  interceptors: {
    request: AxiosInterceptorManager<AxiosRequestConfig>;
    response: AxiosInterceptorManager<AxiosResponse>;
  };
}

export interface AxiosInterceptorManager<V> {
  use(onFulfilled?: (value: V) => V | Promise<V>, 
    onRejected?: (error: any) => any): number;
  eject(id: number): void;
}

對於請求重試的功能來說,我們希望讓用戶不僅能夠設置重試次數,而且可以設置重試延時時間。當請求失敗的時候,若該請求的配置對象配置了重試次數,而 Axios 就會重新發起請求進行重試操作。爲了能夠全局進行請求重試,接下來我們在響應攔截器上來實現請求重試功能,具體代碼如下所示:

axios.interceptors.response.use(null, (err) => {
  let config = err.config;
  if (!config || !config.retryTimes) return Promise.reject(err);
  const { __retryCount = 0, retryDelay = 300, retryTimes } = config;
  // 在請求對象上設置重試次數
  config.__retryCount = __retryCount;
  // 判斷是否超過了重試次數
  if (__retryCount >= retryTimes) {
    return Promise.reject(err);
  }
  // 增加重試次數
  config.__retryCount++;
  // 延時處理
  const delay = new Promise((resolve) => {
    setTimeout(() => {
      resolve();
    }, retryDelay);
  });
  // 重新發起請求
  return delay.then(function () {
    return axios(config);
  });
});

以上的代碼並不會複雜,對應的處理流程如下圖所示:

介紹完如何使用攔截器實現請求重試的功能之後,下面阿寶哥來介紹適配器實現請求重試的方案。

二、適配器實現請求重試的方案

Axios 引入了適配器,使得它可以同時支持瀏覽器和 Node.js 環境。對於瀏覽器環境來說,它通過封裝 XMLHttpRequest API 來發送 HTTP 請求,而對於 Node.js 環境來說,它通過封裝 Node.js 內置的 http 和 https 模塊來發送 HTTP 請求。

在 Axios 如何緩存請求數據? 這篇文章中,阿寶哥介紹瞭如何通過增強默認的 Axios 適配器,來實現緩存請求數據的功能。同樣,採用類似的思路,我們也可以通過增強默認的  Axios 適配器來實現請求重試的功能。

在介紹如何增強默認適配器之前,我們先來看一下 Axios 內置的 xhrAdapter 適配器,它被定義在 lib/adapters/xhr.js 文件中:

// lib/adapters/xhr.js
module.exports = function xhrAdapter(config) {
  return new Promise(function dispatchXhrRequest(resolve, reject) {
    var requestData = config.data;
    var requestHeaders = config.headers;

    var request = new XMLHttpRequest();
    // 省略大部分代碼
    var fullPath = buildFullPath(config.baseURL, config.url);
    request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true);
    // Set the request timeout in MS
    request.timeout = config.timeout;

    // Listen for ready state
    request.onreadystatechange = function handleLoad() { ... }

    // Send the request
    request.send(requestData);
  });
};

很明顯 xhrAdapter 適配器是一個函數對象,它接收一個 config 參數並返回一個 Promise 對象。而在 xhrAdapter 適配器內部,最終會使用 XMLHttpRequest API 來發送 HTTP 請求。爲了實現請求重試的功能,我們就可以考慮通過高階函數來增強 xhrAdapter 適配器的功能。

2.1 定義 retryAdapterEnhancer 函數

爲了讓用戶能夠更靈活地控制請求重試的功能,我們定義了一個 retryAdapterEnhancer 函數,該函數支持兩個參數:

瞭解完 retryAdapterEnhancer 函數的參數之後,我們來看一下該函數的具體實現:

function retryAdapterEnhancer(adapter, options) {
  const { times = 0, delay = 300 } = options;

  return async (config) => {
    const { retryTimes = times, retryDelay = delay } = config;
    let __retryCount = 0;
    const request = async () => {
      try {
        return await adapter(config);
      } catch (err) {
        // 判斷是否進行重試
        if (!retryTimes || __retryCount >= retryTimes) {
          return Promise.reject(err);
        }
        __retryCount++; // 增加重試次數
        // 延時處理
        const delay = new Promise((resolve) => {
          setTimeout(() => {
            resolve();
          }, retryDelay);
         });
         // 重新發起請求
         return delay.then(() => {
           return request();
         });
        }
      };
   return request();
  };
}

以上的代碼並不會複雜,核心的處理邏輯如下圖所示:

2.2 使用 retryAdapterEnhancer 函數

2.2.1 創建 Axios 對象並配置 adapter 選項
const http = axios.create({
  baseURL: "http://localhost:3000/",
  adapter: retryAdapterEnhancer(axios.defaults.adapter, {
    retryDelay: 1000,
  }),
});
2.2.2 使用 http 對象發送請求
// 請求失敗不重試
function requestWithoutRetry() {
  http.get("/users");
}

// 請求失敗重試
function requestWithRetry() {
  http.get("/users", { retryTimes: 2 });
}

好了,如何通過增強 xhrAdapter 適配器來實現 Axios 請求重試的功能已經介紹完了。由於完整的示例代碼內容比較多,阿寶哥就不放具體的代碼了。感興趣的小夥伴,可以訪問以下地址瀏覽示例代碼。

完整的示例代碼:

https://gist.github.com/semlinker/979ebc659abacea7aa6c0c44af070afe

這裏我們來看一下 Axios 實現請求重試示例的運行結果:

三、總結

本文介紹了在 Axios 中如何實現請求重試,基於文中定義的 retryAdapterEnhancer 函數或響應攔截器,你可以輕鬆地擴展請求重試的功能。

Axios 是一個很優秀的開源項目,裏面有很多值得我們學習與借鑑的地方。如果你對 Axios 內部 HTTP 攔截器的設計與實現、HTTP 適配器的設計與實現及如何防禦 CSRF 攻擊感興趣的話,可以閱讀 77.9K 的 Axios 項目有哪些值得借鑑的地方 這篇文章。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/8RJSBwCDTvwX3Oql31ckkg