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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 06:16:19