Axios 在反应中使用 Abort 控制器抛出 CanceledError

ajh*_*ery 14 reactjs axios next.js

我已经构建了一个带有拦截器的 axios 私有实例来管理身份验证请求。

\n

系统有一个自定义的axios实例:

\n
const BASE_URL = 'http://localhost:8000';\nexport const axiosPrivate = axios.create({\n  baseURL: BASE_URL,\n  headers: {\n    'Content-Type': 'application/json',\n  },\n  withCredentials: true,\n});\n
Run Code Online (Sandbox Code Playgroud)\n

自定义 useRefreshToken 挂钩使用刷新令牌返回 accessToken:

\n
const useRefreshToken = () => {\n  const { setAuth } = useAuth();\n\n  const refresh = async () => {\n    const response = await refreshTokens();\n    // console.log('response', response);\n    const { user, roles, accessToken } = response.data;\n    setAuth({ user, roles, accessToken });\n    // return accessToken for use in axiosClient\n    return accessToken;\n  };\n\n  return refresh;\n};\n\nexport default useRefreshToken;\n
Run Code Online (Sandbox Code Playgroud)\n

axios 拦截器在 useAxiosPrivate.js 文件中附加到此 axios 实例,以附加 accessToken,以请求并在过期时使用刷新令牌刷新 accessToken。

\n
const useAxiosPrivate = () => {\n  const { auth } = useAuth();\n  const refresh = useRefreshToken();\n\n  useEffect(() => {\n    const requestIntercept = axiosPrivate.interceptors.request.use(\n      (config) => {\n        // attach the access token to the request if missing\n        if (!config.headers['Authorization']) {\n          config.headers['Authorization'] = `Bearer ${auth?.accessToken}`;\n        }\n        return config;\n      },\n      (error) => Promise.reject(error)\n    );\n\n    const responseIntercept = axiosPrivate.interceptors.response.use(\n      (response) => response,\n      async (error) => {\n        const prevRequest = error?.config;\n        // sent = custom property, after 1st request - sent = true, so no looping requests\n        if (error?.response?.status === 403 && !prevRequest?.sent) {\n          prevRequest.sent = true;\n          const newAccessToken = await refresh();\n          prevRequest.headers['Authorization'] = `Bearer ${newAccessToken}`;\n          return axiosPrivate(prevRequest);\n        }\n        return Promise.reject(error);\n      }\n    );\n\n    // remove the interceptor when the component unmounts\n    return () => {\n      axiosPrivate.interceptors.response.eject(responseIntercept);\n      axiosPrivate.interceptors.request.eject(requestIntercept);\n    };\n  }, [auth, refresh]);\n\n  return axiosPrivate;\n};\n\nexport default useAxiosPrivate;\n
Run Code Online (Sandbox Code Playgroud)\n

现在,这个私有 axios 实例在功能组件 - PanelLayout 中调用,它用于环绕页面并提供布局。

\n

在这里,我尝试在 axios 中使用 AbortControllers 在组件挂载后终止请求。

\n
function PanelLayout({ children, title }) {\n  const [user, setUser] = useState(null);\n  const axiosPrivate = useAxiosPrivate();\n  const router = useRouter();\n\n  useEffect(() => {\n    let isMounted = true;\n    const controller = new AbortController();\n    const signal = controller.signal;\n\n    const getUserProfile = async () => {\n      try {\n        const response = await axiosPrivate.get('/api/identity/profile', {\n          signal,\n        });\n        console.log(response.data);\n        isMounted && setUser(response.data.user);\n      } catch (error) {\n        console.log(error);\n        router.push({\n          pathname: '/seller/auth/login',\n          query: { from: router.pathname },\n        });\n      }\n    };\n    getUserProfile();\n\n    return () => {\n      isMounted = false;\n      controller.abort();\n    };\n  }, []);\n\n  console.log('page rendered');\n\n  return (\n    <div className='flex items-start'>\n      <Sidebar className='h-screen w-[10rem]' />\n      <section className='min-h-screen flex flex-col'>\n        <PanelHeader title={title} classname='left-[10rem] h-[3.5rem]' />\n        <main className='mt-[3.5rem] flex-1'>{children}</main>\n      </section>\n    </div>\n  );\n}\n\nexport default PanelLayout;\n
Run Code Online (Sandbox Code Playgroud)\n

但是,上面的代码抛出以下错误:

\n
CanceledError {message: 'canceled', name: 'CanceledError', code: 'ERR_CANCELED'}\ncode: "ERR_CANCELED"\nmessage: "canceled"\nname: "CanceledError"\n[[Prototype]]: AxiosError\nconstructor: \xc6\x92 CanceledError(message)\n__CANCEL__: true\n[[Prototype]]: Error\n
Run Code Online (Sandbox Code Playgroud)\n

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

\n

Ern*_*bua 15

我也遇到了同样的问题,我认为我的逻辑存在一些缺陷,导致组件被安装了两次。经过一番挖掘后,我发现 React 显然在 StrictMode 的新版本 18 中添加了此功能,其中 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
Run Code Online (Sandbox Code Playgroud)

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