跪拜 Guibai
← Back to the summary

AbortController Is a Universal Kill Switch for Events, Streams, and Timers

Problem Scenario

The team received a requirement: the left side of the page has a real-time log stream (SSE) that takes 3-5 seconds, and processing must stop when the user switches to the right-side Tab. Your first reaction is:

// When switching Tabs: forcibly discard data, but the stream still runs in the background
onTabChange(() => {
  ignoreLogs = true;
});

The result? The network connection isn't broken, memory keeps growing, and events keep firing. The console is full of setState warnings: "Can't perform a React state update on an unmounted component".

This is where AbortController can save the day—but most people only know how to use it to cancel fetch.


Cause Analysis

The core of AbortController is a signal mechanism: create a signal object, pass it to an asynchronous operation, and whenever you want to stop, call controller.abort(). All operations listening to this signal receive the termination notice simultaneously.

But the following three use cases are unknown to 90% of frontend developers.


Solution

🥇 Use Case 1: Using AbortSignal to Cancel Event Listeners

This is the most stunning one—event listeners can work without removeEventListener.

const controller = new AbortController();
const { signal } = controller;

// Pass signal as the third argument
window.addEventListener('resize', handleResize, { signal });
document.addEventListener('click', handleClick, { signal });

// 👇 One-click removal of both listeners
controller.abort();

Real-world scenario: When a modal component mounts, register global keyboard events (ESC to close). When closing, clean up all listeners in one go without calling removeEventListener individually, and no fear of anonymous functions.

useEffect(() => {
  const ctrl = new AbortController();
  document.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') closeModal();
  }, { signal: ctrl.signal });
  return () => ctrl.abort(); // cleanup
}, []);

⚡ Note: The same signal can only use abort() once; afterwards, the signal state is aborted.


🥇 Use Case 2: Using AbortSignal to Interrupt ReadableStream / Async Iterator

Your SSE real-time log reading uses for await...of. How do you interrupt it gracefully?

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(); // proactively release
        break;
      }
      processLog(decoder.decode(value));
    }
  } catch (err) {
    if (err.name !== 'AbortError') throw err;
  }
}

Or more concisely—pass the signal directly to the stream:

const res = await fetch(url, { signal });
const reader = res.body.getReader({ signal }); // 👈 bind signal directly

When abort() is triggered, reader.read() automatically throws an AbortError, a perfect interruption.


🥇 Use Case 3: Using AbortSignal to Encapsulate a "Timeout Cancellation" Pattern

AbortSignal.timeout() is a static method that creates a signal that automatically aborts after N milliseconds:

const res = await fetch(url, {
  signal: AbortSignal.timeout(3000) // automatically cancels after 3 seconds
});

This is a native API, requires no polyfill, and is ten times more elegant than hand-writing setTimeout + controller.

Even better is combining signals:

const ctrl = new AbortController();
const timeoutSignal = AbortSignal.timeout(5000);

// If either signal triggers, it counts as an abort
fetch(url, {
  signal: anySignal([ctrl.signal, timeoutSignal])
});

// User manually cancels
ctrl.abort();

anySignal comes from @rails/request.js or you can implement it yourself. The latest Chrome in 2025 has experimental support for native AbortSignal.any() composition.


Key Takeaways

Use Case In One Sentence What It Replaces
Cancel Event Listeners Pass { signal } to addEventListener removeEventListener + function reference
Interrupt Stream Bind signal to reader Manual reader.cancel() handling
Timeout + Combined Cancellation AbortSignal.timeout() + combined signals setTimeout + custom state variables

One-sentence summary: AbortController is the "universal emergency stop button" of the JS asynchronous world. Don't just use it in fetch—events, streams, timers—any API that supports signal can be gracefully aborted. Say goodbye to memory leaks, starting with mastering AbortController.