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

React中调用AbortController.abort()后fetch请求仍返回结果的问题

问题:React中AbortController取消fetch请求无效的排查与解决

我在React中实现一个功能:当旧的fetch请求未完成时,发起新请求则取消旧请求并调用新请求。之前在useEffect钩子中实现过该功能且运行正常,但本次用普通函数实现时,尽管调用了signal的abort()方法,fetch请求的结果仍会显示。

原代码

let submitDebouncer;
let submitAborter = null;

function sleep(ms){
    return new Promise(resolve => setTimeout(resolve, ms))
}

async function fetcher(url){
    if(submitAborter){
        console.log(submitAborter);
        submitAborter.abort();
        console.log(submitAborter);
    }
    submitAborter = new AbortController();
    const runSignal = submitAborter.signal;

    try{
        const response = await fetch(url, {runSignal});
        const data = await response.json();
        submitAborter = null;
        setDisplayData(data);
        console.log(data);
    } catch(e){
        console.log(e.name)
    }
}

function handleSubmit(){
    if(submitDebouncer){
        clearTimeout(submitDebouncer);
    }
    setLoading(true)
    submitDebouncer = setTimeout(() => {
        const url = `http://api.openweathermap.org/geo/1.0/direct?q=${city}&limit=1&appid=${API_Key}`;
        fetcher(url)
        sleep(50)
        fetcher(url)
    }, 200)
}

输出显示aborted状态已设为true,但仍收到请求结果,预期旧请求会被中止,不会显示冗余结果。


问题排查与修正方案

核心问题点

  1. fetch参数传递错误:你用了{runSignal}的对象简写,但fetch要求的参数键是signal,这导致实际没有把AbortSignal传给fetch,旧请求自然不会被中止。
  2. Controller覆盖时机错误:调用abort()后立刻把submitAborter替换为新的AbortController,导致第一个请求的signal丢失,后续abort操作无法影响它。
  3. 异步操作未正确等待:sleep(50)后直接调用fetcher(url),sleep不会等待,两个请求几乎同时发起,逻辑不符合预期。
  4. 中止后仍可能执行状态更新:即使请求被中止,若响应已经返回,仍会执行setDisplayData,没有额外检查signal状态。

修正后的代码

let submitDebouncer;
let submitAborter = null;

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
}

async function fetcher(url) {
    // 中止之前的请求
    if (submitAborter) {
        submitAborter.abort();
    }
    // 创建新控制器并保存,避免过早覆盖旧控制器
    const currentController = new AbortController();
    submitAborter = currentController;
    const signal = currentController.signal;

    try {
        // 正确传递signal参数给fetch
        const response = await fetch(url, { signal });
        // 检查是否已中止,避免处理后续逻辑
        if (signal.aborted) return;
        
        const data = await response.json();
        // 解析JSON后再次检查,防止过程中请求被中止
        if (signal.aborted) return;
        
        setDisplayData(data);
        console.log(data);
    } catch (e) {
        // 忽略中止错误,只处理其他请求异常
        if (e.name !== 'AbortError') {
            console.error('请求出错:', e);
        }
    } finally {
        // 仅当当前控制器仍是全局保存的实例时才清空,避免影响后续请求
        if (submitAborter === currentController) {
            submitAborter = null;
        }
    }
}

function handleSubmit() {
    if (submitDebouncer) {
        clearTimeout(submitDebouncer);
    }
    setLoading(true);
    submitDebouncer = setTimeout(() => {
        const url = `http://api.openweathermap.org/geo/1.0/direct?q=${city}&limit=1&appid=${API_Key}`;
        fetcher(url);
        // 正确等待sleep完成后再发起新请求
        sleep(50).then(() => {
            fetcher(url);
        });
    }, 200);
}

关键修正说明

  • 确保fetch接收正确的signal参数,让请求能响应中止信号。
  • 先保存新控制器再发起请求,避免旧请求的signal被提前覆盖。
  • 在请求响应和JSON解析后都检查signal状态,防止中止后仍执行状态更新。
  • 用then等待sleep完成,确保两次请求有50ms间隔,符合测试逻辑。
  • 在finally中判断后再清空控制器,避免误删后续请求的控制器实例。

内容的提问来源于stack exchange,提问作者Rohan Garg

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 17:43:10