React中Axios拦截器未等待失败请求的问题排查
说明
受保护路由中的请求需在验证完成后才可处理,Access Token和Refresh Token存储在Cookie中并自动附加至所有必要请求。
问题
当Access Token过期后,后端返回400状态码,原本应该在Axios拦截器中执行刷新Token逻辑,但实际流程是先执行了受保护路由的验证请求,之后才执行刷新操作,导致页面直接跳转到首页,没有利用刷新后的Token重新验证。
截图

Axios拦截器代码
import axios from 'axios'; import { API } from '@/constants/api'; import AuthService from '@/services/AuthService'; export const axiosInstance = axios.create({ baseURL: API.base, timeout: 15000, withCredentials: true, }); axiosInstance.interceptors.request.use( async (config) => config, async (error) => Promise.reject(error), ); axiosInstance.interceptors.response.use( async (response) => response, async (error) => { const config = error?.config; if (error?.response?.status === 400 && !config?.sent) { await AuthService.refreshTokenPair(); return axiosInstance.request(config); } return Promise.reject(error); }, ); export default axiosInstance;
受保护路由代码
const ProtectedRoute = observer((): JSX.Element => { const { isAuth } = userStore; const handleUnauthorized = () => { userStore.isAuth = false; }; useLayoutEffect(() => { const verify = async () => { try { const verifyDetails = await AuthService.verifyAccessToken(); const details = await UserService.getUserDetails(verifyDetails?.id); if (!details) { handleUnauthorized(); return; } userStore.user = details; userStore.isAuth = true; } catch (err) { handleUnauthorized(); } }; verify(); }, []); if (!isAuth) { return <Navigate to={ROUTES.home.path} replace />; } return <Outlet />; }); export default memo(ProtectedRoute);
问题分析与解决方案
核心问题
- Axios拦截器未标记
config.sent,可能导致同一请求重复触发刷新逻辑;且刷新Token后重试请求时,受保护路由的验证流程已经因初始请求失败执行了跳转逻辑。 - 受保护路由的
verify函数在请求失败后直接执行handleUnauthorized,没有考虑拦截器刷新Token后重试的可能性。
修复步骤
完善Axios拦截器的刷新标记
在进入刷新逻辑时给请求配置添加sent标记,避免重复刷新:axiosInstance.interceptors.response.use( async (response) => response, async (error) => { const config = error?.config; if (error?.response?.status === 400 && !config?.sent) { config.sent = true; // 添加标记,防止重复触发刷新 await AuthService.refreshTokenPair(); return axiosInstance.request(config); } return Promise.reject(error); }, );修改ProtectedRoute的验证逻辑
在verify函数捕获错误后,判断是否可通过刷新Token重试验证,而非直接跳转:useLayoutEffect(() => { const verify = async () => { try { const verifyDetails = await AuthService.verifyAccessToken(); const details = await UserService.getUserDetails(verifyDetails?.id); if (!details) { handleUnauthorized(); return; } userStore.user = details; userStore.isAuth = true; } catch (err) { // 尝试刷新Token后重新验证 try { await AuthService.refreshTokenPair(); // 刷新成功后重新执行验证流程 const verifyDetails = await AuthService.verifyAccessToken(); const details = await UserService.getUserDetails(verifyDetails?.id); if (details) { userStore.user = details; userStore.isAuth = true; return; } } catch (refreshErr) { // 刷新Token失败再执行跳转 handleUnauthorized(); } handleUnauthorized(); } }; verify(); }, []);优化状态联动(可选)
在状态管理(如userStore)中添加tokenRefreshed状态,刷新Token后更新该状态,让useLayoutEffect依赖此状态自动重新触发验证:// 拦截器中刷新Token后更新状态 await AuthService.refreshTokenPair(); userStore.tokenRefreshed = !userStore.tokenRefreshed; // ProtectedRoute的useLayoutEffect添加依赖 useLayoutEffect(() => { // ... 验证逻辑 }, [userStore.tokenRefreshed]);
调整后,当Access Token过期时,拦截器会先完成Token刷新,再重试验证请求,受保护路由会根据重试结果判断是否跳转,避免提前触发首页跳转逻辑。
内容的提问来源于stack exchange,提问作者Reverie Metherlance
相关产品推荐
相关产品推荐

