AbortController 不止能取消 Fetch!3 个你不知道的隐藏用法

问题场景

团队来了个需求:页面左侧有一个耗时 3-5 秒的实时日志流(SSE),用户切到右侧 Tab 时必须停止处理。你第一反应是:

js 复制代码
// 切 Tab 时:强行丢弃数据,但流还在背后跑
onTabChange(() => {
  ignoreLogs = true;
});

结果?网络没断、内存还在涨、事件还在触发。控制台一堆 setState 警告:"Can't perform a React state update on an unmounted component"。

这时候 AbortController 能救场------但大部分人只知道用它取消 fetch。


原因分析

AbortController 的核心是一个 信号机制 :创建一个 signal 对象,传递给异步操作,什么时候你想停了,调用 controller.abort(),所有监听这个 signal 的操作同时收到终止通知。

但下面这三个用法,90% 的前端不知道。


解决方案

🥇 用法一:用 AbortSignal 取消事件监听

这是最惊艳的------事件监听器可以不用 removeEventListener。

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

// 把 signal 作为第三个参数传进去
window.addEventListener('resize', handleResize, { signal });
document.addEventListener('click', handleClick, { signal });

// 👇 一键清除两个监听器
controller.abort();

实战场景: 弹窗/Modal 组件挂载时注册全局键盘事件(ESC 关闭),关闭时一键清理所有监听,无需逐个 removeEventListener 也不怕匿名函数。

js 复制代码
useEffect(() => {
  const ctrl = new AbortController();
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') closeModal();
  }, { signal: ctrl.signal });
  return () => ctrl.abort(); // 清理
}, []);

⚡ 注意:同一个 signal 只能用一次 abort(),之后 signal 状态就是 aborted。


🥇 用法二:用 AbortSignal 中断 ReadableStream / Async Iterator

你的 SSE 实时日志读取用了 for await...of,怎么优雅中断?

js 复制代码
async function readLogs(url, signal) {
  const res = await fetch(url, { signal });
  const reader = res.body.getReader();
  const decoder = new TextDecoder();

  try {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      if (signal.aborted) {
        await reader.cancel(); // 主动释放
        break;
      }
      processLog(decoder.decode(value));
    }
  } catch (err) {
    if (err.name !== 'AbortError') throw err;
  }
}

或者更简洁------直接把 signal 传给 stream:

js 复制代码
const res = await fetch(url, { signal });
const reader = res.body.getReader({ signal }); // 👈 signal 直接绑定

当 abort() 触发时,reader.read() 自动抛出 AbortError,完美中断。


🥇 用法三:用 AbortSignal 封装"超时取消"模式

AbortSignal.timeout() 是一个静态方法 ,创建一个 N 毫秒后自动 abort 的信号:

js 复制代码
const res = await fetch(url, {
  signal: AbortSignal.timeout(3000) // 3秒超时自动取消
});

这是原生 API,不需要任何 polyfill,比手写 setTimeout + controller 优雅十倍。

更绝的是组合信号:

js 复制代码
const ctrl = new AbortController();
const timeoutSignal = AbortSignal.timeout(5000);

// 两个信号任意一个触发都算中止
fetch(url, {
  signal: anySignal([ctrl.signal, timeoutSignal])
});

// 用户手动取消
ctrl.abort();

anySignal 来自 @rails/request.js 或者自己实现。2025 年最新版 Chrome 已实验性支持 AbortSignal.any() 原生组合。


要点总结

用法 一句话 替代了什么
取消事件监听 addEventListener 传 { signal } removeEventListener + 函数引用
中断 Stream 绑定 signal 到 reader reader.cancel() 手动处理
超时 + 组合取消 AbortSignal.timeout() + 组合信号 setTimeout + 自定义状态变量

一句话总结: AbortController 是 JS 异步世界的"万能急停按钮"。不只在 fetch 里用,事件、流、定时器 ------任何支持 signal 的 API 都能被优雅中止。告别内存泄漏,从善用 AbortController 开始。

相关推荐
deli0078 分钟前
Redis 缓存穿透、击穿、雪崩:9 组流量实测数据库怎么被打穿
前端
江华森13 分钟前
5Mbps 小水管也能扛:K3s 上 k6 压测 + HPA 弹性伸缩实测
前端
shuangyueliao21 分钟前
103 个小游戏的商店素材,我一张图都没手动做:截图 / 封面 / 打包 / 提审全自动
前端
用户27639657651022 分钟前
放弃 JWT 后,我给 FastAPI 游戏服务端手撸了一套 token session
javascript
ZhengEnCi22 分钟前
前端项目为什么需要一份统一的 theme.css:用 CSS 变量管住全局样式与主题切换
前端·css·前端工程化
wordbaby30 分钟前
一次微信小程序 `web-view` 跳转原生页回跳后 H5 白屏的排查与修复(Hash 路由污染)
前端
用户7218804383532 分钟前
密码骰子:结合AIUI在眼镜上生成一串16位密码
前端
江华森1 小时前
全链路可观测落地记:kube-prometheus-stack 在 K3s 上的 8 个坑与一整套修复脚本
前端
用户84298142418101 小时前
从 JavaScript 到可运行的字节码 - ByteJS 使用指南
javascript
deli0071 小时前
27 寸 4K 开 125% 为什么发虚?PPI 和缩放一次算清
前端