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

如何在axios拦截器中等待内部axios post请求执行完成

问题根因

你所有写法失效的核心原因有两个:

  • Axios请求拦截器的成功回调如果返回普通值,会立即发起请求;如果返回Promise,会等待Promise resolve后再发起请求。你之前用的自执行async函数(IIFE)本身是独立的异步任务,外层回调不会等待它执行完成,会直接返回未更新的旧config,导致请求携带旧token发出。
  • await关键字只能在async声明的函数内部使用,你之前在普通同步回调里直接写await,必然抛出Unexpected reserved word 'await'语法错误。
    另外你当前的实现还遗漏了并发场景的处理:如果页面同时发起多个携带过期token的请求,会重复触发多次刷新接口的调用,引发异常。
可直接运行的正确实现

首先需要给刷新逻辑加并发锁和请求队列,同时把拦截器回调声明为async函数,让拦截器等待token刷新完成后再返回配置:

// 刷新锁:标记当前是否正在执行token刷新,防止并发重复调用刷新接口
let isRefreshing = false;
// 请求队列:存储token刷新期间挂起的请求,待新token获取后统一重发
let pendingRequestQueue = [];

instance.interceptors.request.use(
  // 直接将回调声明为async函数,内部可合法使用await,Axios会等待异步逻辑执行完成再取config发请求
  async (config) => {
    const accessToken = localStorage.getItem("access_token");
    // 无本地token直接返回配置(比如首次登录、未登录场景)
    if (!accessToken) return config;

    const tokenPayload = jwt_decode(accessToken);
    const expireTimestamp = tokenPayload.exp * 1000;
    const currentTimestamp = Date.now();

    // token未过期,直接挂载Authorization头返回
    if (expireTimestamp > currentTimestamp) {
      config.headers["Authorization"] = `Bearer ${accessToken}`;
      return config;
    }

    // 若当前已经在执行刷新逻辑,将当前请求挂起存入队列
    if (isRefreshing) {
      return new Promise((resolve) => {
        pendingRequestQueue.push((newToken) => {
          config.headers["Authorization"] = `Bearer ${newToken}`;
          resolve(config);
        });
      });
    }

    // 加锁,开始执行刷新流程
    isRefreshing = true;
    try {
      const refreshToken = localStorage.getItem("refresh_token");
      // 等待刷新接口返回
      const refreshRes = await refreshAccessToken(refreshToken);
      const newAccessToken = refreshRes.data.accessToken;

      // 更新本地存储和全局鉴权状态
      localStorage.setItem("access_token", newAccessToken);
      setAuth({ token: newAccessToken, refreshToken });

      // 给当前请求挂载新token
      config.headers["Authorization"] = `Bearer ${newAccessToken}`;

      // 执行队列中所有挂起的请求,传入新token
      pendingRequestQueue.forEach((resolveRequest) => resolveRequest(newAccessToken));
      // 清空队列
      pendingRequestQueue = [];

      return config;
    } catch (refreshError) {
      // 刷新失败处理:清除本地凭证、跳转登录页
      localStorage.removeItem("access_token");
      localStorage.removeItem("refresh_token");
      // 可在此处添加跳转登录页逻辑,例如 router.push('/login')
      return Promise.reject(refreshError);
    } finally {
      // 无论刷新成功/失败,都释放刷新锁
      isRefreshing = false;
    }
  },
  (error) => Promise.reject(error)
);

刷新token的方法保持原有写法即可,本身返回Promise对象可直接被await,注意必须使用未挂载当前拦截器的独立axios实例发起刷新请求,避免触发死循环:

const refreshAccessToken = (token) => {
  return refreshInstance.post("/refresh", token);
};
关键注意事项
  • 不要在拦截器内用自执行async函数包裹异步逻辑:这类写法不会阻塞外层回调的返回,拦截器会在异步逻辑执行完成前就发起请求。
  • 刷新token的接口必须使用独立的axios实例(即代码中的refreshInstance):如果用挂载了当前拦截器的instance发刷新请求,刷新请求本身会被拦截器捕获到token过期,触发无限递归调用刷新接口的死循环。
  • 并发锁和请求队列是生产环境必须的逻辑:如果页面同时发起2个及以上携带过期token的请求,没有锁的情况下会同时触发多次刷新接口调用,导致旧token被覆盖、请求失败。
  • 你之前遇到的await语法错误,本质是没有将包含await的回调函数声明为async,只要把request.use的第一个回调加上async前缀即可合法使用await。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 09:51:47