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

使用AbortController取消延迟Fetch请求:两种实现差异原因解析

为什么两种AbortController实现会有差异?

场景背景

我们需要实现:点击按钮发起fetch请求时,若重复点击,则取消之前正在进行的请求,仅保留最新请求。为模拟网络延迟,将fetch包装在带setTimeout的Promise中。初始实现未达到预期,修改后逻辑正常,核心差异在于AbortController的管理时机和作用域。


初始失败实现的问题

function fetchProducts() {
  return fetchDataWithDelay(API_BASE_URL + '/products.json');
}

function fetchDataWithDelay(url, delay = 3000) {
  if (!url) throw new Error('URL is required.');

  if (controller) {
    controller.abort('Fetch aborted by the user.');
    console.log('Fetch aborted.');
  }
  controller = new AbortController(); // 覆盖全局controller

  return new Promise((resolve, reject) => {
    console.log(`Fetching data with ${delay}ms delay...`);
    setTimeout(async () => {
      // 这里引用的是全局controller的最终值
      const response = await fetch(url, { signal: controller.signal });
      // ...后续处理
    }, delay);
  });
}

问题根源

当快速重复点击按钮时:

  1. 第一次调用fetchDataWithDelay:创建controller A并赋值给全局变量,然后启动setTimeout(3秒后执行fetch)。
  2. 第二次调用fetchDataWithDelay:先abort全局的controller A,然后创建controller B覆盖全局变量,启动新的setTimeout。
  3. 3秒后,第一次请求的setTimeout执行fetch,但此时全局controller已经是B了——fetch用的是未被abort的controller B的signal,所以请求正常完成,不会被取消。

简单说:旧请求的fetch最终引用的是后续请求创建的新controller,旧controller的abort操作根本没作用到旧请求的fetch上。


修改后成功实现的逻辑

function fetchProducts() {
  if (controller) {
    controller.abort('Fetch aborted by the user.');
    console.log('Fetch aborted.');
  }
  controller = new AbortController(); // 创建当前请求的controller
  return fetchDataWithDelay(API_BASE_URL + '/products.json', controller); // 传递给延迟函数
}

function fetchDataWithDelay(url, abortController, delay = 3000) {
  if (!url) throw new Error('URL is required.');

  return new Promise((resolve, reject) => {
    console.log(`Fetching data with ${delay}ms delay...`);
    setTimeout(async () => {
      // 引用的是当前请求专属的controller的signal
      const response = await fetch(url, { signal: abortController.signal });
      // ...后续处理
    }, delay);
  });
}

正确逻辑分析

当重复点击时:

  1. 第一次调用fetchProducts:创建controller A,传递给fetchDataWithDelay,启动setTimeout(fetch将使用controller A的signal)。
  2. 第二次调用fetchProducts:先abort全局的controller A,此时controller A的signal触发abort事件;然后创建controller B,传递给新的fetchDataWithDelay。
  3. 3秒后,第一次请求的setTimeout执行fetch,此时用的是已经被abort的controller A的signal,fetch直接抛出取消错误,请求被终止。
  4. 第二次请求的fetch使用专属的controller B的signal,直到下一次点击被abort。

核心差异总结

  • 初始实现中,延迟执行的fetch依赖全局变量的动态值,旧请求的signal被后续请求的controller覆盖,导致abort操作无效。
  • 修改后实现中,每个请求的fetch绑定专属的controller实例,后续请求abort的是旧请求对应的controller,确保旧请求能接收到取消信号。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 07:45:11