You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Axios拦截器处理JWT刷新后 原请求仍捕获错误问题排查

问题根因

你的令牌刷新逻辑执行正常,但原请求还是拿到过期错误,核心是响应拦截器的Promise链断裂,具体有3个直接错误:

  • 处理Expired JWT Token的逻辑分支没有返回任何Promise:Axios响应拦截器的错误回调要求必须返回Promise(要么resolve重试结果,要么reject错误),你当前分支里只是调用了刷新令牌的逻辑但没有return,拦截器默认返回undefined,原请求的Promise链会直接把最初的令牌过期错误抛给上层业务。
  • 重试原请求时用了全局axios对象而非你创建的api实例:全局axios没有配置你预设的baseURL、请求拦截器逻辑,会导致重试请求地址错误、逻辑不生效。
  • 包裹重试请求的Promise写法错误:你写的new Promise(() => {})没有接收resolve/reject参数,就算重试请求成功拿到结果,也无法把结果传递回原Promise链,这个Promise会一直处于pending状态。

额外隐患:没有做并发请求锁,当多个请求同时触发令牌过期时,会并发发起多次令牌刷新请求,极易导致refresh_token失效。

修复代码

首先替换你原有的响应拦截器部分代码,修复上述问题同时加上并发锁避免重复刷新:

// 新增:令牌刷新锁与等待队列,避免并发重复刷新
let isRefreshing = false;
let failedQueue = [];

// 队列处理工具:刷新完成后批量重试队列里的请求,刷新失败则批量抛错
const processQueue = (error, token = null) => {
  failedQueue.forEach(prom => {
    if (error) {
      prom.reject(error);
    } else {
      prom.resolve(token);
    }
  })
  failedQueue = [];
}

api.interceptors.response.use(
  function (response) {
    return response;
  },
  async function (error) {
    const originalRequest = error.config;
    // 先做错误存在性判断,避免网络错误时取data报错
    if (!error.response) {
      return Promise.reject(error);
    }
    if (error.response.data.message === "Expired JWT Token" && !originalRequest._retry) {
      // 标记当前请求已重试,避免死循环
      originalRequest._retry = true;

      // 如果正在刷新令牌,就把当前请求加入等待队列
      if (isRefreshing) {
        return new Promise(function(resolve, reject) {
          failedQueue.push({resolve, reject})
        }).then(token => {
          originalRequest.headers.Authorization = `Bearer ${token}`;
          return api(originalRequest);
        }).catch(err => {
          return Promise.reject(err);
        })
      }

      isRefreshing = true;
      try {
        const { data } = await api.post("/token/refresh", { refresh_token: storage.getRefreshToken() });
        storage.setTokens(data.token, data.refresh_token);
        // 处理队列里等待的请求
        processQueue(null, data.token);
        // 重试当前请求,注意用api实例而非全局axios
        originalRequest.headers.Authorization = `Bearer ${data.token}`;
        return api(originalRequest);
      } catch (refreshError) {
        processQueue(refreshError, null);
        // 刷新失败走登出逻辑
        console.error(refreshError);
        store().dispatch("auth/logout").then(() => {
          router().push({
            name: "register-login",
            query: { error: "invalid_token" },
          });
          router().go(0);
          store().dispatch("setLoading", false);
        });
        return Promise.reject(refreshError);
      } finally {
        isRefreshing = false;
      }
    } else if (error.response.data.message === "Invalid JWT Token") {
      console.log("error");
      await store().dispatch("auth/logout");
      router().push({
        name: "register-login",
        query: { error: "invalid_token" },
      });
      router().go(0);
      store().dispatch("setLoading", false);
      return Promise.reject(error);
    } else {
      return Promise.reject(error);
    }
  }
);
修复点说明
  • 所有错误处理分支都明确返回了Promise,不会再出现默认返回undefined导致原错误直接透传的问题。
  • 重试请求统一使用配置好的api实例,保证baseURL、拦截器逻辑一致。
  • 去掉了写法错误的无resolve/reject的Promise包裹,重试成功后直接把api(originalRequest)的结果返回,结果可以正常透传到业务层的then回调。
  • 新增了刷新锁和等待队列,多个请求同时过期时只会发起一次刷新请求,刷新完成后批量重试所有挂起的请求,避免重复调用刷新接口。
  • 给原请求加了_retry标记,避免重试后依然返回过期错误时触发无限刷新循环。
  • 增加了网络错误场景的判断,避免error.response不存在时读取data抛出JS异常。

另外你业务侧的请求写法可以简化,不需要额外包一层Promise,直接return api调用的结果即可:

export function sendTags(context, payload) {
  return api.post("/spot/addTags", payload).then(({ data }) => data);
}

内容的提问来源于stack exchange,提问作者GontranMacedoine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 20:31:06