使用axios拦截器优化带token刷新的请求处理函数的实现方案
现有代码问题分析
当前实现存在几个明显的可优化点:
- 每次调用
handleFetchData都会注册一次响应拦截器,多次调用后会出现拦截逻辑重复执行的问题,可能导致重复触发dispatch、token重复刷新等异常行为 - token刷新逻辑和
/me接口的业务逻辑强耦合,后续其他接口如果也需要处理token过期场景无法复用这套逻辑 - 多层Promise嵌套形成回调地狱,代码可读性差,且没有异常捕获逻辑,刷新token、重试请求失败时没有兜底处理
- 重复编写完整接口路径、Authorization请求头,后续接口地址变更时维护成本很高
优化实现方案
我们可以把axios实例、全局拦截器统一抽离,和业务逻辑拆分,实现通用的token自动刷新能力。
第一步:抽离全局axios实例配置
单独创建request.js文件,统一配置axios实例、请求/响应拦截器:
import axios from 'axios'; // 创建统一的axios实例,配置基础地址 const authAxios = axios.create({ baseURL: 'http://142.93.134.108:1111' }); // 请求拦截器:自动给请求头加Authorization authAxios.interceptors.request.use((config) => { const accessToken = localStorage.getItem('accessToken'); if (accessToken) { config.headers.Authorization = `Bearer ${accessToken}`; } return config; }); // 响应拦截器:统一处理token过期刷新,并重试原请求 let isRefreshing = false; // 防止同时触发多个刷新token请求 let pendingRequests = []; // 存储token刷新期间的待重试请求 authAxios.interceptors.response.use( (response) => { // 业务状态码正常直接返回结果 if (response.data.statusCode === 200) { return response; } // 业务状态码异常,判定为token过期 const originalRequest = response.config; if (!isRefreshing) { isRefreshing = true; // 调用刷新token接口 return axios.post(`${authAxios.defaults.baseURL}/refresh`, {}, { headers: { Authorization: `Bearer ${localStorage.getItem('refreshToken')}` } }).then(res => { // 更新本地存储的token const { access_token: newAccessToken, refresh_token: newRefreshToken } = res.data.body; localStorage.setItem('accessToken', newAccessToken); localStorage.setItem('refreshToken', newRefreshToken); // 刷新token后重试之前所有 pending 的请求 pendingRequests.forEach(cb => cb(newAccessToken)); pendingRequests = []; // 重试当前触发过期的请求 originalRequest.headers.Authorization = `Bearer ${newAccessToken}`; return authAxios(originalRequest); }).catch(err => { // 刷新token失败,一般是refreshToken也过期了,这里可以加跳转到登录页的逻辑 console.error('token刷新失败,请重新登录', err); localStorage.removeItem('accessToken'); localStorage.removeItem('refreshToken'); return Promise.reject(err); }).finally(() => { isRefreshing = false; }); } else { // 正在刷新token时,把后续请求存到pending队列 return new Promise(resolve => { pendingRequests.push((newToken) => { originalRequest.headers.Authorization = `Bearer ${newToken}`; resolve(authAxios(originalRequest)); }); }); } }, (error) => { // http状态码异常的处理逻辑,可按需扩展 return Promise.reject(error); } ); export default authAxios;
第二步:简化业务函数
拆分后原来的handleFetchData只需要处理业务逻辑即可,非常简洁:
import authAxios from './request.js'; export const handleFetchData = () => async (dispatch) => { try { const response = await authAxios.get('/me'); dispatch({ type: "USER_ME", payload: response }); } catch (err) { // 按需添加请求失败的业务处理,比如提示用户登录过期 console.error('获取用户信息失败', err); } };
优化后优势
- 拦截器全局仅注册一次,不会出现重复执行的问题
- token刷新逻辑全局复用,所有使用
authAxios实例发送的请求都会自动处理token过期场景 - 用async/await代替嵌套Promise回调,代码可读性更高,增加了异常捕获逻辑避免页面报错
- 统一配置接口基础地址和请求头,后续修改无需逐个调整接口
内容的提问来源于stack exchange,提问作者kyrylolv
相关产品推荐
相关产品推荐

