You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 14:33:10