Axios 请求重试

场景题: “你在项目中封装了 Axios 请求层。当请求因为网络波动、超时或服务端暂时不可用而失败时,如何重新发起原请求?设计重试机制时还要考虑哪些问题?”

核心回答

Axios 请求失败后,可以在响应拦截器中从 error.config 取得原始请求配置,记录当前重试次数,等待一段时间后调用封装的 Axios 实例 request(config) 重新发起请求。

不能无条件重试。完整方案还要做到:

  1. 限制最大重试次数,避免无限循环。
  2. 只重试网络错误、超时、429 和部分 5xx
  3. 使用指数退避,避免连续请求进一步压垮服务端。
  4. 默认只重试幂等请求;POST 等非幂等请求需要业务提供幂等键。
  5. 用户主动取消的请求不重试。

参考实现

import axios, {
  AxiosError,
  InternalAxiosRequestConfig,
} from 'axios';

interface RetryConfig extends InternalAxiosRequestConfig {
  retry?: number;
  retryCount?: number;
}

const request = axios.create({
  baseURL: '/api',
  timeout: 10_000,
});

request.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    const config = error.config as RetryConfig | undefined;

    if (!config || !shouldRetry(error)) {
      return Promise.reject(error);
    }

    const maxRetry = config.retry ?? 3;
    config.retryCount = config.retryCount ?? 0;

    if (config.retryCount >= maxRetry) {
      return Promise.reject(error);
    }

    config.retryCount += 1;
    await sleep(300 * 2 ** (config.retryCount - 1));

    // 重新调用封装后的实例,baseURL 和拦截器仍然生效
    return request(config);
  },
);

function shouldRetry(error: AxiosError) {
  const method = error.config?.method?.toLowerCase();
  const status = error.response?.status;
  const retryableMethod = ['get', 'head', 'options', 'put', 'delete'].includes(
    method ?? '',
  );
  const retryableError =
    !error.response ||
    error.code === 'ECONNABORTED' ||
    status === 429 ||
    (status !== undefined && status >= 500);

  return retryableMethod && retryableError;
}

function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

面试官追问

Q1:为什么调用 request(config),而不是直接调用 axios(config)

因为自定义实例中可能配置了 baseURL、超时时间和拦截器。使用同一个实例重放请求,才能保持请求行为一致;重试次数会阻止响应拦截器无限递归。

Q2:401 应该直接重试吗?

不应该。401 通常要先刷新 Token,再重放原请求。多个请求同时返回 401 时,可共享同一个刷新 Promise,避免重复刷新 Token。

Q3:POST 为什么不能随意重试?

它可能造成重复下单、重复扣款等副作用。确实需要重试时,应由服务端支持幂等键,或由业务明确确认该接口可安全重放。

一句话总结

error.config 取回原请求配置,在配置上维护重试次数,经过错误筛选和指数退避后调用 Axios 实例重新发起;同时必须处理幂等性、取消请求和 Token 刷新等边界。