如何取消Fetch请求?React场景下旧请求终止失败问题
问题与解决方案
问题描述
我有一个通过邮箱搜索用户的表单,需求是点击搜索按钮时自动取消最早发起的请求。接口设置了5秒超时,即如果在第一个请求发起的5秒内发起第二个请求,应该只返回第二个请求的用户数据。我使用React Context存储查询结果,尝试用AbortController实现请求取消,但未生效——两次请求的结果都会返回,先显示第一个用户数据,1秒后再显示第二个。
现有代码
Context 实现代码
const { createContext, useState, useContext} = require("react") const FindedDataContext = createContext() export const useFindedData = () => useContext(FindedDataContext) export const FindedDataProvider = ({children}) =>{ const [findedData, setFindedData] = useState() const abortController = new AbortController() const cancelLastFetch = async () => abortController.abort() const findData = async ({email, number}) =>{ const {signal} = abortController console.log(signal); cancelLastFetch() return await fetch('http://localhost:5000/api?' + new URLSearchParams({ email, number }, {signal}) ) .then(res => res.json()) .then(data => setFindedData(data)) .catch(err => console.log(err)) } return( <FindedDataContext.Provider value={{findedData, findData, cancelLastFetch}}> {children} </FindedDataContext.Provider> ) }
搜索按钮提交代码
const submitForm = async () =>{ setValidationError({...validationError, email: null}) await findData(formData) }
问题根源
- fetch参数传递错误:
{signal}被错误传入URLSearchParams的参数列表,导致fetch根本没有绑定AbortController的signal,终止请求的操作完全无效。 - 控制器复用问题:仅创建了一个AbortController实例,发起新请求时复用已终止的控制器,无法正确管理新请求的生命周期,且请求发起前就终止了当前控制器,逻辑顺序错误。
修正后的代码
Context 代码
const { createContext, useState, useContext, useRef } = require("react") const FindedDataContext = createContext() export const useFindedData = () => useContext(FindedDataContext) export const FindedDataProvider = ({children}) =>{ const [findedData, setFindedData] = useState() // 用useRef存储控制器,保留跨渲染的引用 const abortControllerRef = useRef(null) const findData = async ({email, number}) =>{ // 终止之前的请求(如果存在) if (abortControllerRef.current) { abortControllerRef.current.abort() } // 创建新的控制器实例 const abortController = new AbortController() abortControllerRef.current = abortController const {signal} = abortController try { const res = await fetch( 'http://localhost:5000/api?' + new URLSearchParams({email, number}), {signal} // 正确传递signal到fetch配置项 ) const data = await res.json() // 仅当当前请求未被终止时更新状态 if (!signal.aborted) { setFindedData(data) } } catch (err) { // 忽略终止请求的预期错误 if (err.name !== 'AbortError') { console.error(err) } } } return( <FindedDataContext.Provider value={{findedData, findData}}> {children} </FindedDataContext.Provider> ) }
关键修改说明
- 用
useRef存储AbortController实例,避免组件重渲染时丢失引用,确保能正确终止上一次请求。 - 每次发起新请求前,先终止上一次的请求,再创建新控制器,保证新请求使用独立的signal。
- 修复fetch参数位置,将
signal正确传入fetch的配置对象中。 - 更新状态前检查请求是否已被终止,避免被取消的请求覆盖最新结果。
- 捕获错误时过滤
AbortError,避免控制台打印预期内的终止报错。
搜索按钮代码保持不变
原提交逻辑无需修改,直接使用即可。
内容的提问来源于stack exchange,提问作者Roman Nichi
相关产品推荐
相关产品推荐

