You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何取消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)
    }

问题根源

  1. fetch参数传递错误:{signal}被错误传入URLSearchParams的参数列表,导致fetch根本没有绑定AbortController的signal,终止请求的操作完全无效。
  2. 控制器复用问题:仅创建了一个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 12:57:31