React使用AbortController时组件渲染即报操作中止错误
问题现象
在React项目中自定义useHttpClient Hook处理接口请求,内部接入AbortController API,期望实现用户跳转路由、组件卸载时自动中止未完成的fetch请求,避免内存泄漏和异常报错。实际运行时组件首次渲染完成就直接抛出「操作已被中止」的错误,中止逻辑触发时机不符合预期。
相关实现代码
Hook 实现代码
import { useEffect , useState , useCallback , useRef } from 'react'; export const useHttpClient = () => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(); const activeHttpRequests = useRef([]); const sendRequest = useCallback(async (URL, method = 'GET', body = null, headers = {}) => { setIsLoading(true); const httpAbortController = new AbortController(); activeHttpRequests.current.push(httpAbortController); try { const res = await fetch(URL, {method, body, headers, signal: httpAbortController.signal}); const resData = await res.json(); activeHttpRequests.current = activeHttpRequests.current.filter(reqCtrl => reqCtrl !== httpAbortController) console.log(activeHttpRequests) if(!res.ok) { throw new Error(resData.error); } setIsLoading(false) return resData; } catch (error) { setIsLoading(false) setError(error.message) throw error; } }, []) const clearError = () => { setError(null); } useEffect(() => { return () => { activeHttpRequests.current.forEach(abortCtrl => abortCtrl.abort()); }; }, []); return {isLoading, error, clearError, sendRequest} }
业务组件使用代码
// Modules import React, { useEffect, useState } from 'react'; // Hooks import { useHttpClient } from '../../../helpers/hooks/http-hook'; // Components import UserList from '../components/user-list/user-list.component'; import ErrorModal from '../../shared/error-modal/error-modal.component'; import Spinner from '../../shared/loading-spinner/spinner.component'; const Users = () => { const { isLoading , error , clearError , sendRequest } = useHttpClient(); const [loadedUsers, setLoadedUsers] = useState(); useEffect(() => { const fetchUsers = async () => { try { const URL = 'http://localhost:5000/api/users'; const res = await sendRequest(URL); setLoadedUsers(res.users); } catch (error) {} } fetchUsers(); }, [sendRequest]) return ( <React.Fragment> <ErrorModal error={error} onClear={clearError} /> { isLoading && <Spinner overlay/> } { !isLoading && loadedUsers && <UserList users={loadedUsers} /> } </React.Fragment> ); } export default Users;
问题根因
触发报错有两个核心原因:
- React 18 开发环境默认开启严格模式(Strict Mode),会对首次挂载的组件自动执行「挂载→卸载→重新挂载」的模拟流程,提前暴露生命周期逻辑隐患。组件第一次挂载时触发
sendRequest,创建的AbortController实例被存入ref,紧接着模拟卸载阶段就会执行useEffect返回的清理函数,调用所有已存储controller的abort()方法,此时请求还在pending状态,直接抛出中止错误。 - 原有catch逻辑没有区分错误类型,把主动中止请求抛出的错误和真实接口错误同等处理,直接写入error状态,导致页面刚渲染就出现报错。
修复方案
不需要移除严格模式(严格模式可以提前帮助排查代码问题,生产环境不会执行重复挂载的模拟逻辑),只需要在错误捕获逻辑中过滤掉主动中止请求产生的错误即可:AbortController主动中止请求抛出的错误,name属性固定为AbortError,这类错误不需要触发错误提示,也不需要向外抛出。
修复后的核心代码
仅需修改sendRequest方法中的catch块逻辑:
// 省略其余未改动代码 try { // 原有请求、响应处理逻辑保持不变 } catch (error) { setIsLoading(false); // 过滤主动中止请求产生的错误 if (error.name !== 'AbortError') { setError(error.message); throw error; } }
修改后,开发环境严格模式下模拟卸载触发的中止错误会被直接忽略,只有真实的接口异常才会触发错误提示;生产环境下用户跳转页面卸载组件时,中止请求产生的错误也不会被误报,完全符合预期效果。
内容的提问来源于stack exchange,提问作者Mahdyar
相关产品推荐
相关产品推荐

