如何用Axios处理主API因令牌过期失败时的刷新重试逻辑?
最优令牌过期处理与Axios请求重试方案
原代码问题分析
- 无限循环根源:当
getToken调用generateToken失败(比如返回非SUCCESS状态),新令牌未更新,但代码依然递归调用原请求,导致反复触发ACCESS_DENIED错误,陷入无限递归。 - 无重试次数限制:即使令牌获取成功,若后续请求因其他原因再次返回
ACCESS_DENIED,也会持续重试。 - headers处理不严谨:直接修改全局
headers变量,可能引发多请求并发时的状态污染。
改进方案
方案1:针对单个请求添加重试控制
先修改getToken,让它返回令牌获取是否成功,方便上层判断是否继续重试:
export const getToken = async () => { try { const response = await generateToken(); if (response.status === 'SUCCESS' && response.message === 'SUCCESS') { saveAccessTokenInLocalStorage(response.data.token); return true; // 返回成功标识 } return false; } catch (error) { console.error('获取新令牌失败:', error); return false; } }
然后修改getAllStates,添加重试次数限制,仅在令牌获取成功时才重试:
// 最多重试1次(避免无限循环,可按需调整) const MAX_RETRY = 1; export const getAllStates = async (payload, retryCount = 0) => { // 每次请求生成独立headers,避免全局变量污染 const headers = { // 保留原有默认headers(如果有),再覆盖Authorization ...(window.defaultHeaders || {}), 'Authorization': getAccessTokenFromLocalStorage() }; try { const response = await axios.get(`${API_URL}/geo/states`, { headers, data: {} }); return response; } catch (error) { console.log('请求错误:', error.message); // 仅在未超重试次数、且是令牌过期错误时尝试重试 if (retryCount < MAX_RETRY && error.response?.data?.status === 'FAILURE' && error.response?.data?.message === 'ACCESS_DENIED') { const tokenUpdated = await getToken(); // 只有令牌更新成功才重试 if (tokenUpdated) { return getAllStates(payload, retryCount + 1); } } // 其他情况返回错误信息 return error?.response || error; } }
方案2:使用Axios拦截器全局处理(更优雅)
单个请求写重试逻辑会重复代码,推荐用Axios拦截器统一处理令牌过期和重试,所有请求自动生效:
// 创建独立Axios实例,避免污染全局Axios配置 const apiClient = axios.create({ baseURL: API_URL }); // 请求拦截器:自动为所有请求注入Authorization头 apiClient.interceptors.request.use( (config) => { const token = getAccessTokenFromLocalStorage(); if (token) { config.headers.Authorization = token; } return config; }, (error) => Promise.reject(error) ); // 响应拦截器:统一处理令牌过期与重试 let isRefreshing = false; let failedRequestsQueue = []; apiClient.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; // 仅处理令牌过期且未重试过的请求 if (error.response?.data?.status === 'FAILURE' && error.response?.data?.message === 'ACCESS_DENIED' && !originalRequest._retry) { // 避免同时触发多个令牌刷新请求 if (isRefreshing) { return new Promise((resolve, reject) => { failedRequestsQueue.push({ resolve, reject }); }).then(token => { originalRequest.headers.Authorization = token; return apiClient(originalRequest); }).catch(err => Promise.reject(err)); } originalRequest._retry = true; isRefreshing = true; try { const tokenResponse = await generateToken(); if (tokenResponse.status === 'SUCCESS' && tokenResponse.message === 'SUCCESS') { const newToken = tokenResponse.data.token; saveAccessTokenInLocalStorage(newToken); // 更新当前请求的令牌并重试 originalRequest.headers.Authorization = newToken; // 重试队列中所有等待的请求 failedRequestsQueue.forEach(({ resolve }) => resolve(newToken)); failedRequestsQueue = []; return apiClient(originalRequest); } else { // 令牌获取失败,清空队列并抛出错误 failedRequestsQueue.forEach(({ reject }) => reject(error)); failedRequestsQueue = []; return Promise.reject(error); } } catch (refreshError) { failedRequestsQueue.forEach(({ reject }) => reject(refreshError)); failedRequestsQueue = []; return Promise.reject(refreshError); } finally { isRefreshing = false; } } return Promise.reject(error); } ); // 使用封装后的apiClient发起请求 export const getAllStates = async (payload) => { try { const response = await apiClient.get('/geo/states', { data: {} }); return response; } catch (error) { console.log('请求错误:', error.message); return error?.response || error; } }
关键优化点
- 重试次数限制:避免无限循环,确保最多重试1次(可根据业务需求调整)。
- 令牌获取状态校验:只有新令牌成功获取并存储后,才重试原请求。
- 全局拦截器:统一处理所有请求的令牌逻辑,避免重复代码,同时解决多请求并发时的令牌刷新冲突(用
isRefreshing标记和请求队列)。 - 避免全局变量污染:每次请求生成独立的headers,或用拦截器自动注入令牌。
内容的提问来源于stack exchange,提问作者Kunal Tyagi
相关产品推荐
相关产品推荐

