Axios抛出CanceledError,中止控制器处于React状态

ar7v8xwq  于 2022-11-05  发布在  iOS
关注(0)|答案(2)|浏览(2269)

我已经构建了一个axios私有示例,它带有拦截器来管理auth请求。
系统有一个自定义axios示例:

const BASE_URL = 'http://localhost:8000';
export const axiosPrivate = axios.create({
  baseURL: BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
  withCredentials: true,
});

自定义useRefreshToken挂接使用刷新标记返回accessToken:

const useRefreshToken = () => {
  const { setAuth } = useAuth();

  const refresh = async () => {
    const response = await refreshTokens();
    // console.log('response', response);
    const { user, roles, accessToken } = response.data;
    setAuth({ user, roles, accessToken });
    // return accessToken for use in axiosClient
    return accessToken;
  };

  return refresh;
};

export default useRefreshToken;

Axios拦截器在useAxiosPrivate.js文件中附加到该axios示例,以附加accessToken,从而在accessToken过期时使用刷新令牌请求和刷新accessToken。

const useAxiosPrivate = () => {
  const { auth } = useAuth();
  const refresh = useRefreshToken();

  useEffect(() => {
    const requestIntercept = axiosPrivate.interceptors.request.use(
      (config) => {
        // attach the access token to the request if missing
        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) => {
        const prevRequest = error?.config;
        // sent = custom property, after 1st request - sent = true, so no looping requests
        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);
      }
    );

    // remove the interceptor when the component unmounts
    return () => {
      axiosPrivate.interceptors.response.eject(responseIntercept);
      axiosPrivate.interceptors.request.eject(requestIntercept);
    };
  }, [auth, refresh]);

  return axiosPrivate;
};

export default useAxiosPrivate;

现在,这个私有axios示例在功能组件- PanelLayout中被调用,该组件用于 Package 页面并提供布局。
在这里,我尝试使用axios中的AbortControllers在组件挂载后终止请求。

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,
        });
        console.log(response.data);
        isMounted && setUser(response.data.user);
      } catch (error) {
        console.log(error);
        router.push({
          pathname: '/seller/auth/login',
          query: { from: router.pathname },
        });
      }
    };
    getUserProfile();

    return () => {
      isMounted = false;
      controller.abort();
    };
  }, []);

  console.log('page rendered');

  return (
    <div className='flex items-start'>
      <Sidebar className='h-screen w-[10rem]' />
      <section className='min-h-screen flex flex-col'>
        <PanelHeader title={title} classname='left-[10rem] h-[3.5rem]' />
        <main className='mt-[3.5rem] flex-1'>{children}</main>
      </section>
    </div>
  );
}

export default PanelLayout;

但是,上述代码会引发以下错误:

CanceledError {message: 'canceled', name: 'CanceledError', code: 'ERR_CANCELED'}
code: "ERR_CANCELED"
message: "canceled"
name: "CanceledError"
[[Prototype]]: AxiosError
constructor: ƒ CanceledError(message)
__CANCEL__: true
[[Prototype]]: Error

请建议如何避免上述错误并使axios正常工作。

x6492ojm

x6492ojm1#

我也遇到了同样的问题,我认为我的逻辑中有一些缺陷,导致组件被安装了两次。在做了一些调查之后,我发现react显然在新版本18的StrictMode中添加了这个功能,其中useEffect被运行了两次。这里有一个链接,指向清楚解释这个新行为的文章。
解决此问题的一种方法是从应用程序中删除StrictMode(临时解决方案)
另一种方法是使用useRef钩子来存储一些状态,这些状态在应用程序第二次挂载时更新。

// CODE BEFORE USE EFFECT

const effectRun = useRef(false);

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,
        });
        console.log(response.data);
        isMounted && setUser(response.data.user);
      } catch (error) {
        console.log(error);
        router.push({
          pathname: '/seller/auth/login',
          query: { from: router.pathname },
        });
      }
    };

    // Check if useEffect has run the first time
    if (effectRun.current) {
      getUserProfile();
    }

    return () => {
      isMounted = false;
      controller.abort();
      effectRun.current = true; // update the value of effectRun to true
    };
  }, []);

 // CODE AFTER USE EFFECT

this YouTube视频中找到了解决方案。

hmmo2u0o

hmmo2u0o2#

我也遇到过这个问题。更糟糕的是,当请求被取消时,axios不提供HTTP状态代码,尽管你得到了error.code === "ERR_CANCELED"。我通过在axios拦截器中处理abort来解决这个问题:

axiosInstance.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.code === "ERR_CANCELED") {
      // aborted in useEffect cleanup
      return Promise.resolve({status: 499})
    }
    else {
      return Promise.reject((error.response && error.response.data) || 'Error')
    }
    }
);

正如您所看到的,我确保了在异常中止的情况下,错误响应提供了status代码499

相关问题