Axios请求拦截器async/await不生效,请求提前发送问题解决
问题
为Axios实例编写了全局请求拦截器,用于在请求前刷新过期的accessToken。拦截器使用async/await处理token刷新的异步请求,但发现token过期后的首次请求会先发送至业务接口(如/users/{id}),再执行token刷新请求,导致该请求因token无效失败,后续请求因localStorage已更新新token而成功。希望在请求阶段处理token刷新,让请求必须等待拦截器的异步操作完成后再执行,而非通过响应拦截器重试请求。
原因分析
你的拦截器代码已经使用了async/await,但存在两个核心问题导致请求未等待异步操作完成:
- 拦截器的错误处理函数未返回
Promise.reject(error),异常时会静默跳过异步流程,直接发送原始请求 - 请求调用时手动携带了旧的
Authorization头,且拦截器未确保覆盖该值;同时未处理多请求并发时的重复刷新问题,可能导致请求提前发送
解决方案
关键修复点
- 确保拦截器返回Promise:Axios会等待请求拦截器返回的Promise完成后再发送请求,
async/await天然返回Promise,只需保证错误被正确抛出 - 覆盖请求自带的旧token:在拦截器中强制更新
config.headers.Authorization,避免请求携带旧token - 避免重复刷新:用全局Promise缓存正在进行的刷新请求,防止多请求并发时重复调用刷新接口
- 正确处理错误:拦截器错误处理函数必须返回
Promise.reject(error),中断错误请求流程
修改后的拦截器代码
// 全局缓存正在进行的token刷新请求,避免重复调用 let refreshTokenPromise = null; instance.interceptors.request.use(async (config) => { const tokens = JSON.parse(localStorage.getItem('users')); if (!tokens) { return config; } // 校验accessToken有效性 const accessTokenValid = Date.now() <= (tokens?.expirationTime ?? Date.now()); if (accessTokenValid) { // 强制覆盖请求自带的Authorization头 config.headers.Authorization = `Bearer ${tokens.accessToken}`; return config; } const refreshToken = tokens.refreshToken; if (!refreshToken) { // 无刷新token时直接抛出错误,中断请求 return Promise.reject(new Error('No refresh token available')); } // 复用正在进行的刷新请求 if (refreshTokenPromise) { await refreshTokenPromise; const newTokens = JSON.parse(localStorage.getItem('users')); config.headers.Authorization = `Bearer ${newTokens.accessToken}`; return config; } try { // 发起token刷新请求并缓存Promise refreshTokenPromise = fetch(`${some-endpoint}/auth/refresh/token?token=${refreshToken}`) .then(response => { if (!response.ok) throw new Error('Token refresh failed'); return response.json(); }) .then(data => { const newTokens = data.data.data; localStorage.setItem('users', JSON.stringify({ accessToken: newTokens.access_token, expirationTime: Date.now() + parseInt(newTokens.expires_in) * 1000 - 600 * 1000, refreshToken: newTokens.refresh_token, })); return newTokens; }) .finally(() => { // 刷新完成后清空缓存 refreshTokenPromise = null; }); const newTokens = await refreshTokenPromise; config.headers.Authorization = `Bearer ${newTokens.access_token}`; return config; } catch (error) { refreshTokenPromise = null; // 刷新失败可在此处跳转登录页 return Promise.reject(error); } }, (error) => { // 错误处理必须返回rejected Promise console.error(error); return Promise.reject(error); });
优化请求调用方式
建议移除请求options中手动携带的Authorization头,统一由拦截器处理,彻底避免旧token干扰:
// 简化请求调用,不再手动携带Authorization return instance.get("/users/" + id);
如果必须保留options,上述拦截器代码会自动覆盖其中的Authorization头,无需额外修改。
内容的提问来源于stack exchange,提问作者Richard
相关产品推荐
相关产品推荐

