React中Axios私有实例结合AbortController抛出CanceledError问题
解决Axios私有实例结合AbortController的CanceledError问题
问题场景
构建了带认证拦截器的Axios私有实例,实现以下功能:
- 自定义Axios实例配置基础参数
- 通过
useRefreshToken钩子刷新令牌获取新AccessToken useAxiosPrivate为实例添加请求/响应拦截器:请求时自动附加AccessToken,响应403时刷新Token并重试请求
在PanelLayout组件中使用该实例调用用户接口,并用AbortController终止组件卸载后的请求时,抛出CanceledError,导致错误处理逻辑(如跳转登录页)被错误触发。
解决方案
核心思路是区分正常的请求取消错误和真实的认证错误,避免对取消请求的错误执行不必要的业务逻辑。
1. 在组件错误捕获中过滤CanceledError
在组件的请求错误捕获逻辑里,判断错误是否为CanceledError,如果是则跳过跳转登录的逻辑。
2. 在响应拦截器中排除CanceledError
在Axios私有实例的响应拦截器中,先检查是否为请求取消错误,直接拒绝该错误,不进入403重试流程,避免重试已取消的请求。
修改后的代码示例
PanelLayout组件错误处理修改
function PanelLayout({ children, title }) { const [user, setUser] = useState(null); const axiosPrivate = useAxiosPrivate(); const router = useRouter(); useEffect(() => { let isMounted = true; const controller = new AbortController(); const signal = controller.signal; const getUserProfile = async () => { try { const response = await axiosPrivate.get('/api/identity/profile', { signal, }); isMounted && setUser(response.data.user); } catch (error) { // 仅处理非请求取消的错误 if (error.code !== 'ERR_CANCELED') { console.log(error); router.push({ pathname: '/seller/auth/login', query: { from: router.pathname }, }); } } }; getUserProfile(); return () => { isMounted = false; controller.abort(); }; }, []); // ... 组件渲染部分不变 }
useAxiosPrivate响应拦截器修改
const useAxiosPrivate = () => { const { auth } = useAuth(); const refresh = useRefreshToken(); useEffect(() => { const requestIntercept = axiosPrivate.interceptors.request.use( (config) => { if (!config.headers['Authorization']) { config.headers['Authorization'] = `Bearer ${auth?.accessToken}`; } return config; }, (error) => Promise.reject(error) ); const responseIntercept = axiosPrivate.interceptors.response.use( (response) => response, async (error) => { // 直接拒绝请求取消错误,不进入重试逻辑 if (error.code === 'ERR_CANCELED') { return Promise.reject(error); } const prevRequest = error?.config; if (error?.response?.status === 403 && !prevRequest?.sent) { prevRequest.sent = true; const newAccessToken = await refresh(); prevRequest.headers['Authorization'] = `Bearer ${newAccessToken}`; return axiosPrivate(prevRequest); } return Promise.reject(error); } ); return () => { axiosPrivate.interceptors.response.eject(responseIntercept); axiosPrivate.interceptors.request.eject(requestIntercept); }; }, [auth, refresh]); return axiosPrivate; };
说明
- 组件卸载时触发
AbortController.abort()属于正常的请求终止,并非认证失败,因此不需要执行跳转登录的逻辑。 - 在响应拦截器中排除
CanceledError,可以避免尝试重试已被取消的请求,减少不必要的资源消耗。
内容的提问来源于stack exchange,提问作者ajhavery
相关产品推荐
相关产品推荐

