Axios 如何实现请求重试?

25,556 阅读6分钟

Axios 如何取消重复请求? 这篇文章中,阿宝哥介绍了在 Axios 中如何取消重复请求及 CancelToken 的工作原理。而本文将介绍在 Axios 中如何通过 拦截器或适配器 来实现请求重试的功能。那么为什么要进行请求重试呢?这是因为在某些情况下,比如请求超时的时候,我们希望能自动重新发起请求进行重试操作,从而完成对应的操作。

下面阿宝哥将介绍如何使用 Axios 提供的拦截器或适配器来实现请求重试的功能,如果你对 Axios 的拦截器和适配器还不熟悉的话,建议先阅读 77.9K 的 Axios 项目有哪些值得借鉴的地方 这篇文章。接下来,我们先来介绍如何使用拦截器实现请求重试的方案。

一、拦截器实现请求重试的方案

Axios 是一个基于 Promise 的 HTTP 客户端,而 HTTP 协议是基于请求和响应:

所以 Axios 提供了 请求拦截器和响应拦截器 来分别处理请求和响应,它们的作用如下:

  • 请求拦截器:该类拦截器的作用是在请求发送前统一执行某些操作,比如在请求头中添加 token 字段。
  • 响应拦截器:该类拦截器的作用是在接收到服务器响应后统一执行某些操作,比如发现响应状态码为 401 时,自动跳转到登录页。

在 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);
  });
});

以上的代码并不会复杂,对应的处理流程如下图所示:

介绍完如何使用拦截器实现请求重试的功能之后,下面阿宝哥来介绍适配器实现请求重试的方案。

关注「全栈修仙之路」阅读阿宝哥原创的 4 本免费电子书(累计下载 3万+)及 50 几篇 TS 系列教程。

二、适配器实现请求重试的方案

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 函数,该函数支持两个参数:

  • adapter:预增强的 Axios 适配器对象;
  • options:缓存配置对象,该对象支持 2 个属性,分别用于配置不同的功能:
    • times:全局设置请求重试的次数;
    • delay:全局设置请求延迟的时间,单位是 ms。

了解完 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 请求重试的功能已经介绍完了。由于完整的示例代码内容比较多,阿宝哥就不放具体的代码了。感兴趣的小伙伴,可以访问以下地址浏览示例代码。

完整的示例代码:gist.github.com/semlinker/9…

这里我们来看一下 Axios 实现请求重试示例的运行结果:

三、总结

本文介绍了在 Axios 中如何实现请求重试,基于文中定义的 retryAdapterEnhancer 函数或响应拦截器,你可以轻松地扩展请求重试的功能。Axios 是一个很优秀的开源项目,里面有很多值得我们学习与借鉴的地方。如果你对 Axios 内部 HTTP 拦截器的设计与实现、HTTP 适配器的设计与实现及如何防御 CSRF 攻击感兴趣的话,可以阅读 77.9K 的 Axios 项目有哪些值得借鉴的地方 这篇文章。

关注「全栈修仙之路」阅读阿宝哥原创的 4 本免费电子书(累计下载 3万+)及 11 篇 Vue 3 进阶系列教程。想一起学习 TS/Vue 3.0 的小伙伴可以添加阿宝哥微信 —— semlinker

四、参考资源