AbortController:让异步操作随时说停就停
一、什么是 AbortController?
AbortController 是 JavaScript 在浏览器和部分 Node.js 环境中提供的全局类,用来中止正在进行或待完成的异步操作(如 fetch() 请求、事件监听、可写流、数据库事务等)。通过它,我们可以在需要的时候自由地终止这些操作,避免不必要的开销或冗长的等待。
1. 核心思路
创建一个 AbortController 实例后,可以通过它的 signal 属性将中止信号传递给相应的 API。当调用 controller.abort() 时,所有使用该信号的操作都会收到中止通知,并根据设置的逻辑停止执行或抛出错误。
二、基础用法
// 创建 AbortController 实例
const controller = new AbortController();
// 拿到 signal,并可将其传入需要可中止的 API
const signal = controller.signal;
// 主动触发中止
controller.abort();
1. 实例 signal 属性
- signal 是一个 AbortSignal 实例,可被用于任何支持中止的 API(如 fetch()、事件监听器等)
- 一旦 abort() 被调用,signal 就会触发 abort 事件,标记为已中止
2. 实例 abort() 方法
- 调用后会让该 signal 上监听的所有异步操作同时中止
- 可选地,abort(reason) 可以传递一个原因或错误,供业务逻辑作更细粒度的处理
3. 监听中止事件
controller.signal.addEventListener('abort', () => {
  // 在这里编写中止后的逻辑,例如清理资源或提示用户
});
三、实用场景与示例
1. 事件监听器自动清理
我们可以在添加事件监听器时将 signal 作为选项的一部分
一旦 abort() 被调用,会自动移除与该 signal 关联的监听器,从而简化了取消事件监听的流程
const controller = new AbortController();
window.addEventListener('resize', () => {
  console.log('Window resized!');
}, { signal: controller.signal });
// 调用 abort(),会自动移除 resize 监听器
document.getElementById('but').onclick = () => {
  controller.abort();
}
1.1. 优势
- 无需手动保存监听器引用再调用 removeEventListener()
- 若有多个监听器共用同一个 signal,只需一次 abort() 就能一并移除
2. 在 React 中的应用示例
useEffect(() => {
  const controller = new AbortController();
  window.addEventListener('resize', handleResize, { signal: controller.signal });
  window.addEventListener('hashchange', handleHashChange, { signal: controller.signal });
  window.addEventListener('storage', handleStorageChange, { signal: controller.signal });
  return () => {
    // 调用一次 abort(),所有监听器全部被移除
    controller.abort();
  };
}, []);
3. 中止 fetch() 请求
fetch() 支持通过 signal 中止 HTTP 请求,是 AbortController 最常见的应用场景之一
async function uploadFile(file) {
  const controller = new AbortController();
  const responsePromise = fetch('/upload', {
    method: 'POST',
    body: file,
    signal: controller.signal,
  });
  return { responsePromise, controller };
}
const { responsePromise, controller } = uploadFile(file);
document.getElementById('but').onclick = () => {
    controller.abort();
}
- 一旦 abort 被触发,fetch() 返回的 Promise 将被拒绝,后端也将收到挂断请求(具体取决于实际网络环境)
4. Node.js 中止 http 请求
在新版 Node.js 环境中(v16+),AbortSignal 同样可以应用于内置的 http 或 https 模块中,以取消请求或响应读取。用法与浏览器环境基本一致
4.1. server.js
const http = require('http');
const server = http.createServer((req, res) => {
  console.log('收到请求:', req.url);
  res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
  // 模拟长时间响应:每 500ms 输出一段文字,共输出 50 次
  let count = 0;
  const intervalId = setInterval(() => {
    if (count < 50) {
      res.write(`数据块 #${count}\n`);
      count++;
    } else {
      clearInterval(intervalId);
      res.end('响应全部完成\n');
    }
  }, 500);
  req.on('close', () => {
    console.log('客户端连接关闭,停止发送数据');
    clearInterval(intervalId);
  });
});
const PORT = 3000;
server.listen(PORT, () => {
  console.log(`服务器已启动,监听端口 ${PORT}`);
});
4.2. controller.js
const http = require('http');
function makeAbortableRequest() {
  const controller = new AbortController();
  const { signal } = controller;
  const req = http.get(
    'http://localhost:3000',
    { signal },
    (res) => {
      console.log('已连接到服务器,响应状态:', res.statusCode);
      res.on('data', (chunk) => {
        console.log('收到数据块:', chunk.toString());
      });
      res.on('end', () => {
        console.log('响应结束');
      });
    }
  );
  req.on('error', (err) => {
    console.error('请求错误:', err.message || err);
  });
  setTimeout(() => {
    console.log('5 秒时间到,准备中止请求...');
    controller.abort();
  }, 5000);
}
makeAbortableRequest();
5. 终止流操作
- 可写流
  - 对于基于 WritableStream 或者更底层的写操作,如果支持将 signal 与写操作关联,当 abort() 被调用时,即可停止写入并执行清理
 
- 自定义可中止逻辑
  - 在数据库事务中,自行监听 signal 的 abort 事件来回滚事务或抛出特定错误,都能让流程变得更灵活
 
async function example() {
  const abortController = new AbortController();
  const stream = new WritableStream({
    write(chunk, sinkController) {
      console.log('正在写入:', chunk);
    },
    close() {
      console.log('写入完成');
    },
    abort(reason) {
      console.warn('写操作被中止:', reason);
    }
  });
  const writer = stream.getWriter();
  let i = 1
  const inter = setInterval(async () => {
    await writer.write(`数据块 ${i}`);
    i++
  }, 1000)
  window.currentAbortController = abortController;
  window.currentWriter = writer;
  document.getElementById('cancelButton').onclick = async () => {
    clearInterval(inter)
    if (window.currentAbortController && window.currentWriter) {
      console.log('点击了取消写操作按钮');
      await window.currentWriter.abort('用户主动终止写入');
      window.currentAbortController.abort('用户主动终止写入');
    } else {
      console.log('没有正在进行的写操作');
    }
  };
}
example();
四、兼容性
- 浏览器

- Node.js
Node.js v16 及以上原生支持在 fetch()、http/https 模块中使用 signal
五、总结与扩展
1. 核心价值
- AbortController 让我们可以更优雅地中止异步操作,不再依赖过时的回调或繁琐的清理逻辑。
- 避免占用资源或等待无效请求,提升性能和用户体验。
2. 应用场景广泛
- 事件监听清理、fetch() 请求取消、Node.js HTTP 请求中止、数据库事务可回滚……几乎所有异步操作都能通过一个 signal 实现“一键叫停”。
3. 常见注意点
- 在代码中使用多条 fetch() 时,务必区分好各自的 controller,或使用 AbortSignal.any() 合并控制。
- 对于中止错误,需要在 .catch() 或事件监听中显式处理,防止误认为是网络异常或其他错误。