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

