首页 > 网页制作 >如何避免 Axios 拦截器无限重试循环

如何避免 Axios 拦截器无限重试循环

来源:互联网 2026-06-20 10:42:02

Token过期触发401时,若刷新令牌请求也被同一拦截器捕获,将导致无限重试循环。根本解法是创建纯净的、无响应拦截器的独立axios实例处理刷新请求,并添加_retry标记与URL排除双重防护,刷新失败时彻底清理状态并跳转登录页。

当 token 过期触发 401 响应时,若刷新令牌请求本身也被同一拦截器捕获,就会反复重试导致无限循环;根本解法是用独立的、无响应拦截器的 axios 实例处理刷新请求。

坦白说,这个问题在项目中挺隐蔽的,但它带来的破坏力可不小。当 Token 过期,服务端返回 401 的时候,拦截器会自动跑进去执行刷新操作。可问题来了:如果刷新令牌的请求(比如 /auth/refresh-token 这个接口)本身也是由同一个配置了响应拦截器的 axiosInstance 发起,那它就又会一头撞进同样的错误处理逻辑里。更糟糕的是,这个刷新请求自己也可能返回 401(比如 Refresh Token 过期了),而且因为没打上 _retry 标记,它就会触发新一轮的刷新调用。一环套一环,最终就成了「请求失败 → 触发刷新 → 刷新请求又被拦截 → 再次失败 → 再次触发刷新」的死循环,页面要么卡死,要么重定向彻底失效。核心问题就卡在这里:刷新逻辑被自己设下的“路障”给绊住了。

核心问题定位

从代码层面来看,隐患的源头非常集中:

长期稳定更新的攒劲资源: >>>点此立即查看<<<

const response = await axiosInstance.get(REFRESH_TOKEN_ENDPOINT); //  危险!

这里的 axiosInstance 已经绑定了 response.interceptor,那么 REFRESH_TOKEN_ENDPOINT 这个请求一旦失败(比如返回 401),它就会不自觉地再次钻进 interceptors.response.use(...) 的 error 分支,然后又去调 refreshAccessToken()。关键是,originalRequest._retry 对这个新生成的刷新请求根本不起作用——因为它就是个全新的请求,没有 _retry 标记。循环自然而然地就形成了。

正确解决方案:隔离刷新请求

答案其实很清晰:必须让刷新 Token 的请求彻底绕过拦截器链。行业里最稳妥的做法是创建一个纯净的、没有任何自定义拦截器的 Axios 实例,专门用来处理认证相关的操作:

// authApi.js —— 纯净实例,仅用于认证接口
import axios from 'axios';
import { REFRESH_TOKEN_ENDPOINT } from '../constants/apiUrlConst';

//  关键:不添加任何 request/response interceptor
export const authApi = axios.create({
  baseURL: process.env.REACT_APP_API_URL || '/api',
  timeout: 10000,
});

然后在 refreshAccessToken 函数里,就改用这个纯净实例:

import { authApi } from './authApi'; // 引入纯净实例
import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY } from '../constants/tokenConst';
import { logout } from '../stores/actions/authActions';
import store from '../stores';

const refreshAccessToken = async () => {
  const refresh_token = localStorage.getItem(REFRESH_TOKEN_KEY);
  if (!refresh_token) {
    store.dispatch(logout());
    localStorage.clear();
    window.location.href = '/';
    throw new Error('No refresh token a vailable');
  }

  try {
    //  使用无拦截器的实例,彻底避免循环
    const response = await authApi.get(REFRESH_TOKEN_ENDPOINT);
    const { access_token, refresh_token: newRefreshToken } = response.data;
    localStorage.setItem(ACCESS_TOKEN_KEY, access_token);
    localStorage.setItem(REFRESH_TOKEN_KEY, newRefreshToken);
    return access_token;
  } catch (error) {
    // 刷新失败:清理状态并强制登出
    store.dispatch(logout());
    localStorage.clear();
    window.location.href = '/login';
    throw error; // 确保上层能捕获
  }
};

同时,主 axiosInstance 的响应拦截器也需要做些调整,加一道额外的保险:

axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    const { config, response } = error;
    //  仅对非刷新请求且未重试过的情况触发刷新
    if (
      response.status === 401 &&
      !config._retry &&
      config.url !== REFRESH_TOKEN_ENDPOINT // 额外保险:显式排除
    ) {
      config._retry = true;
      try {
        const newToken = await refreshAccessToken();
        config.headers.Authorization = `Bearer ${newToken}`;
        return axiosInstance(config); // 重发原始请求
      } catch (e) {
        return Promise.reject(e);
      }
    }
    return Promise.reject(error);
  });

注意事项与最佳实践

  • 永远不要在刷新逻辑中使用带响应拦截器的实例——这是一道红线,绕过去就容易出事。
  • 添加 _retry 标记 + URL 排除双重防护:避免极端情况下出现误触发,多一道保障总没坏处。
  • 刷新失败必须彻底清理状态:清除 localStorage、dispatch logout、跳转登录页,一个都不能少,防止残留的无效 Token 造成更多混乱。
  • 服务端配合同样关键:确保 /refresh-token 接口不校验 Authorization 头,只验证 Refresh Token 本身的有效性,不然客户端再优化也是巧妇难为无米之炊。
  • 考虑并发请求的场景:如果多个请求几乎同时返回 401,可以引入 Promise 缓存机制(比如用一个 pendingRefresh 变量来管理),避免多次并发刷新带来的额外开销和状态混乱。

把认证流程和业务请求完全解耦,无限循环的问题就能一劳永逸地解决。这样,Token 刷新就会变得健壮、可预测,调试起来也清爽很多。

侠游戏发布此文仅为了传递信息,不代表侠游戏网站认同其观点或证实其描述

热游推荐

更多
湘ICP备14008430号-1 湘公网安备 43070302000280号
All Rights Reserved
本站为非盈利网站,不接受任何广告。本站所有软件,都由网友
上传,如有侵犯你的版权,请发邮件给xiayx666@163.com
抵制不良色情、反动、暴力游戏。注意自我保护,谨防受骗上当。
适度游戏益脑,沉迷游戏伤身。合理安排时间,享受健康生活。