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
相关产品推荐
相关产品推荐

