✂️ AbortController:一个 API 统一取消 fetch、事件监听与 AI 流式请求

问题场景

页面里经常需要"取消"一个进行中的异步操作:

  • 用户点了一个慢请求,又点了另一个,想让前一个作废(避免旧数据覆盖新数据)。
  • 搜索框防抖后,用户很快又输入------要取消上一次未完成的搜索
  • 组件卸载(切页面)时,取消还没回来的请求,防止 setState to unmounted 警告。
  • AI 对话流式输出中,用户点"停止"------要中断服务端生成。

你可能会各写各的:fetch 手动忽略、监听用 removeEventListener、AI 请求干脆留着它跑完......结果一坨混乱、还浪费流量和 token

AbortController 就是为"取消"设计的统一标准 API------一个 controller,同时管 fetch、事件监听、流式读取。

原因分析

fetch 和其他异步 API 需要一种"外部主动中止"的信号机制。早期方案五花八门(XHR.abort()、手动 flag 变量判断),不统一、难组合。

AbortController 提供标准化的信号(signal)传播 :你创建一个 controller,拿到 signal,把它传给任何支持 signal 参数的 API;需要取消时调用 controller.abort(),所有绑定了这个 signal 的操作同时收到中止信号。

解决方案

1. 基础:取消 fetch(旧页面 vue 风格通用,这里以原生为例)

js 复制代码
const controller = new AbortController();
const { signal } = controller;

fetch('/api/search?q=vue', { signal })
  .then(r => r.json())
  .then(data => render(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('请求已被取消,忽略');
      return;   // 别把 abort 当错误弹给用户
    }
    throw err;
  });

// 需要取消时:
controller.abort();

关键点:捕获错误时必须判断 err.name === 'AbortError',否则会把"主动取消"误当成网络错误报错。

2. 核心痛点场景:旧请求作废(竞态保护)

js 复制代码
let currentController = null;

async function search(keyword) {
  // 如果上一个请求还在,先取消它
  if (currentController) currentController.abort();

  currentController = new AbortController();
  try {
    const res = await fetch(`/api/search?q=${keyword}`, {
      signal: currentController.signal
    });
    render(await res.json());
  } catch (e) {
    if (e.name === 'AbortError') return; // 被新的搜索顶掉了,忽略
    showError(e);
  }
}

这样永远只有最后一次搜索的结果会渲染,快速输入时不再有"旧结果覆盖新结果"的闪屏。

3. 取消事件监听(比 removeEventListener 更省心)

AbortController 也能管理事件监听------一个 controller 批量移除,不用记住每个 handler 引用:

js 复制代码
const controller = new AbortController();
const { signal } = controller;

window.addEventListener('scroll', onScroll, { signal });
window.addEventListener('resize', onResize, { signal });

// 一键撤销所有监听:
controller.abort();
// 需要时再传 { once: true } 与 signal 组合

组件卸载时调用 controller.abort(),比手动 removeEventListener 逐个清理干净且不易漏。

4. 取消流式读取(SSE / AI 对话)

对 AI 流式或 response.body 的读取,abort 能真正中断

js 复制代码
const controller = new AbortController();

async function chat(prompt) {
  const res = await fetch('/api/chat/stream', {
    method: 'POST',
    body: JSON.stringify({ prompt }),
    signal: controller.signal      // 传入 signal
  });
  const reader = res.body.getReader();
  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      appendChunk(new TextDecoder().decode(value));
    }
  } catch (e) {
    if (e.name === 'AbortError') console.log('用户停止了生成');
  }
}

// "停止"按钮:
document.querySelector('#stop').onclick = () => controller.abort();

服务端收到 TCP 断开/中止后会停止生成,还能帮你省 AI token------比"前端不管、让它在后台跑完"经济得多。

5. 超时自动取消

js 复制代码
function fetchWithTimeout(url, ms = 8000) {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), ms);
  return fetch(url, { signal: controller.signal })
    .finally(() => clearTimeout(timer));
}

要点总结

  • AbortController = 一个 controller + 一个 signalabort() 统一中止所有绑定该 signal 的操作。
  • catch 里判断 AbortError:把主动取消和真实报错分开,别把取消弹成错误。
  • 竞态保护(搜索/请求覆盖)用"先 abort 旧的再发新的",保证只渲染最后一次。
  • 事件监听支持 { signal }controller.abort() 批量移除,告别逐个 removeEventListener
  • 流式读取(AI 对话/SSE)abort 能真正中断,省 token、省流量
  • 兼容性:现代浏览器全支持XMLHttpRequest 老接口不适用(fetch 时代的标配)。
  • 结合前端框架:组件卸载时在 useEffect/onUnmounted 里 abort,根治"setState on unmounted"警告。

一句话:AbortController 是前端异步世界的"统一取消键"------fetch、事件、流式一把梭。学会它,竞态、内存泄漏、浪费 token 这些老毛病一次根治。

相关推荐
mldong1 天前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排1 天前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60611 天前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角1 天前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!1 天前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰1 天前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi1 天前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen1 天前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马1 天前
AI源码分析:拆解模型底层实现逻辑
前端
IT_陈寒1 天前
React子组件莫名其妙重渲染?你可能漏了这个Hook
前端·人工智能·后端