React中useEffect外声明AbortController卸载时抛未处理AbortError
问题说明
在React组件useEffect外部声明AbortController实例,在useEffect清理函数中调用controller.abort()实现卸载时中断请求,组件卸载时抛出未捕获异常:
Unhandled Rejection (AbortError): The operation was aborted.
问题代码如下:
const controller = new AbortController() function MyComponent() { useEffect(() => { return () => controller.abort() }, []) const fetchData = async () => { try { const data = await fetchMyData(controller) } catch(error) { if (error.name === "AbortError") return console.log("Error ", error) } } }
错误堆栈截图:
写法存在的问题
- AbortController作用域错误:将
AbortController实例声明在组件外部属于模块级全局变量,不归单个组件实例所有。如果页面同时渲染多个MyComponent,所有组件会共用同一个controller,任意一个组件卸载触发abort都会中断所有组件的请求;且AbortController是一次性实例,触发abort后状态无法重置,后续组件发起的所有绑定该signal的请求都会直接被中断。 - Abort错误未被正确捕获:这是触发本次报错的直接原因。全局声明的controller生命周期和组件、请求生命周期完全解耦:如果组件卸载调用abort时,请求还未发起、或者请求已经结束、又或者
fetchMyData内部没有将abort产生的rejection正确抛出到外层try/catch块,AbortError就不会被业务逻辑捕获,成为全局未处理的Promise rejection。如果开启了React严格模式,useEffect会重复执行挂载-清理流程,第一次清理就会把全局controller置为aborted状态,后续请求发起时会直接抛出未捕获的AbortError。
修复方案
AbortController必须和组件/单次请求的生命周期绑定,不要声明为全局变量,根据业务场景选择对应写法:
场景1:组件挂载时自动发起请求
将controller声明在useEffect内部,和组件生命周期强绑定,保证每个组件实例、每次effect执行都生成全新的controller:
function MyComponent() { useEffect(() => { // 每个组件实例、每次effect执行都生成独立controller const controller = new AbortController() const fetchData = async () => { try { // 注意fetch等原生API接收的是controller.signal,不是controller实例本身 const data = await fetchMyData(controller.signal) // 处理正常返回数据 } catch(error) { // 主动中断的请求直接忽略 if (error.name === "AbortError") return console.log("请求异常: ", error) } } fetchData() // 卸载时中断当前组件的未完成请求 return () => controller.abort() }, []) // 其余组件逻辑 }
场景2:交互事件触发请求(如按钮点击)
用useRef存储controller实例,每次发起新请求前先中断上一次未完成的请求,同时在组件卸载时做兜底中断:
import { useEffect, useRef, useCallback } from 'react' function MyComponent() { const controllerRef = useRef(null) const fetchData = useCallback(async () => { // 中断上一次未完成的重复请求 if (controllerRef.current) { controllerRef.current.abort() } // 新请求生成全新controller controllerRef.current = new AbortController() try { const data = await fetchMyData(controllerRef.current.signal) // 处理正常业务逻辑 } catch(error) { if (error.name === "AbortError") return console.log("请求异常: ", error) } }, []) useEffect(() => { // 组件卸载兜底中断 return () => { if (controllerRef.current) { controllerRef.current.abort() } } }, []) return <button onClick={fetchData}>拉取数据</button> }
内容的提问来源于stack exchange,提问作者user3476614
相关产品推荐
相关产品推荐

