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

