You need to enable JavaScript to run this app. React场景实现fetch拦截器:401刷新refresh token后重试原请求问题求解
优惠活动
大模型
产品
解决方案
定价
更多

React场景实现fetch拦截器:401刷新refresh token后重试原请求问题求解

自定义Fetch拦截器实现401自动刷新鉴权重试

原有实现的核心问题

  • Promise链断裂:异步的token刷新流程没有被await,执行到判断401的分支时,直接返回了初始的401响应,根本没等刷新流程完成
  • 上下文丢失:没有保存原请求的入参,刷新完成后无法复刻原请求的参数发起重试
  • 无并发控制:如果多个接口同时返回401,会触发多次重复的token刷新请求,容易触发后端鉴权校验异常
  • 递归风险:如果调用刷新接口时直接使用被重写后的fetch,会触发拦截器的循环判断,导致无限递归

完整可落地的实现代码

(function setupFetchAuthInterceptor() {
  // 保存原生fetch引用,避免递归调用重写后的方法
  const originalFetch = window.fetch;
  // 全局锁:维护单次token刷新的Promise,避免并发重复刷新
  let refreshingTokenPromise = null;

  // 重写全局fetch,保持和原生fetch一致的入参出参规则
  window.fetch = async function(...requestArgs) {
    // 先执行一次原请求
    let response = await originalFetch.apply(this, requestArgs);

    // 仅针对401状态触发鉴权流程
    if (response.status === 401) {
      // 识别刷新接口本身的请求,避免循环触发
      const requestUrl = requestArgs[0];
      const targetUrl = typeof requestUrl === 'string' 
        ? requestUrl 
        : requestUrl?.url || '';
      const isRefreshApiCall = targetUrl.includes('/api/token');

      // 刷新接口本身返回401,直接返回结果让上层处理登出逻辑
      if (isRefreshApiCall) {
        return response;
      }

      try {
        // 无正在进行的刷新请求时,发起一次新的token刷新
        if (!refreshingTokenPromise) {
          refreshingTokenPromise = originalFetch('/api/token', {
            method: 'POST',
            credentials: 'include',
            headers: {
              'Content-Type': 'application/json'
            }
          }).then((refreshRes) => {
            if (refreshRes.status !== 200) {
              throw new Error('Refresh token failed');
            }
            return refreshRes;
          }).finally(() => {
            // 刷新流程结束后清空锁状态,后续401请求可重新触发刷新
            refreshingTokenPromise = null;
          });
        }

        // 所有触发401的请求统一等待同一个刷新流程完成
        await refreshingTokenPromise;

        // 刷新成功后,用原请求的所有参数重试一次
        response = await originalFetch.apply(this, requestArgs);
      } catch (refreshError) {
        // 刷新失败时,直接返回最初的401响应,让上层做跳转登录等处理
        return response;
      }
    }

    // 返回最终的处理后的响应给业务调用方
    return response;
  };
})();

方案匹配的核心逻辑覆盖

  • 自动检测所有经过fetch发起的请求是否返回401状态,业务代码无需单独编写重复的判断分支
  • 触发401时自动发起/api/token的刷新请求,无需业务层手动调用
  • 若刷新接口本身返回401,直接返回该响应,不会触发循环重试
  • 若刷新接口返回200,自动使用原请求的参数重新发起调用,业务层拿到的就是带正确鉴权信息的正常响应
  • 全局Promise锁机制保证同时多个401请求只会触发一次token刷新,避免并发请求冲突

针对你提到的两个具体问题的解决思路

  1. 原请求重试实现
    不需要额外引入第三方重试类包,在重写fetch的过程中会完整保留所有原请求的入参(包括请求地址、请求方法、请求头等所有配置),等token刷新流程走完后,直接将保存的入参传给原生fetch发起重试即可,这种方式比通用重试包的适配性更高,完全匹配鉴权场景的重试规则(仅在401时重试1次,不做无限重试)。
  2. async/await逻辑修正
    之前的问题根源是在Promise链的then回调里调用了异步的刷新流程,但没有对这个异步流程做await等待,导致代码直接执行到return分支,返回了初始的401响应。修正后的实现将整个重写的fetch方法声明为async函数,所有异步步骤(发原请求、等token刷新、重试原请求)都按顺序await等待,保证整个流程完成后才返回最终响应,不会出现提前返回undefined或者初始401状态的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.10 10:24:06