Axios 如何實現請求重試?

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

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

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

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

在 Axios 中設置攔截器很簡單,通過 axios.interceptors.requestaxios.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 內置的 httphttps 模塊來發送 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