Web Dev Simplified Blog

These Advanced AbortController Features Are Amazing

June 23, 2025

Introduction

If you have made API requests then you have probably used AbortController to cancel fetch requests that are no longer needed. This is especially popular in frameworks like React.

What you probably didn’t know, though, is that this is only a small percentage of what AbortController can do, and in this article I will be covering all the amazing features of AbortController that nearly no one knows.

If you prefer to learn visually, check out the video version of this article.

The Basics of AbortController

Before we dive into the advanced features I do need to cover the basics so feel free to skip this section if you are already familiar with AbortController.

The AbortController API works by creating a signal that you can pass to certain JavaScript functions. This signal tells the function if it should continue or abort. In order to abort a function you just need to call controller.abort(), and any function that is listening to that signal will stop what it is doing.

const controller = new AbortController()
const signal = controller.signal

fetch("/api/data", { signal })
  .then(res => res.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === "AbortError") {
      console.log("Request was aborted")
    } else {
      throw err
    }
  })

// Cancel the request
controller.abort()

Fetch requests are the most common use case for AbortController, but it can be used with any API that supports the signal option.

AbortController With Event Listeners

You can also use AbortController to remove event listeners.

const controller = new AbortController()
const signal = controller.signal

window.addEventListener("resize", () => console.log("Resized"), {
  signal,
})

// Later, this removes the listener
controller.abort()

When you create an event listener you can pass along a third parameter which is an options object. If you pass the signal property with the AbortController’s signal, then when you call controller.abort(), it will automatically remove the event listener as if you called removeEventListener.

This is especially useful in frameworks like React where you want to clean up event listeners from useEffect, or when you have many event listeners you want to remove at the same time.

useEffect(() => {
  const controller = new AbortController()
  const signal = controller.signal

  window.addEventListener("dragstart", () => console.log("Drag started"), {
    signal,
  })
  window.addEventListener("dragend", () => console.log("Drag ended"), {
    signal,
  })

  return () => {
    // Removes all the listeners
    controller.abort()
  }
}, [])

AbortSignal Built In Functions

The AbortController API also has some built-in functions that can make your life easier through the AbortSignal interface.

AbortSignal.timeout()

One of the most underused features of AbortController is AbortSignal.timeout(), which creates a signal that automatically aborts after a timeout:

const signal = AbortSignal.timeout(5000) // 5 seconds

fetch("/api/slow-endpoint", { signal }).catch(err => {
  if (err.name === "TimeoutError") {
    console.log("Request timed out")
  }
})

The above code creates a fetch request that will automatically abort after 5 seconds if it hasn’t completed.

No longer do you need to manually handle timeouts with setTimeout and clearTimeout.

AbortSignal.any()

Another powerful feature is AbortSignal.any(), which allows you to create a signal that aborts when any of the provided signals are aborted:

const controller = new AbortController()
const signal = AbortSignal.any(
  controller.signal,
  AbortSignal.timeout(3000), // 3 seconds
)

This is great if you want to combine a timeout and a manual abort signal. The request will be aborted if either the manual abort is called or the timeout is reached.

AbortSignal.abort()

You can also create an AbortSignal that is already aborted using AbortSignal.abort():

const signal = AbortSignal.abort()
fetch("/api/data", { signal }).catch(err => {
  if (err.name === "AbortError") {
    console.log("Request was aborted")
  }
})

This is probably the least useful of the built-in functions, but it can be handy in certain situations.

Create Your Own AbortController Enabled Functions

The true power in AbortController comes from the ability to create your own functions that support aborting. This allows you to build APIs that are cancelable, just like fetch.

Doing this is as simple as accepting a signal parameter in your function, checking if it has been aborted, and then listening for the abort event:

function doSomething(signal) {
  return new Promise((resolve, reject) => {
    // Is it already aborted?
    if (signal.aborted) {
      reject(signal.reason)
      return
    }

    // Listen for abort events
    signal.addEventListener("abort", () => {
      clearTimeout(id)
      reject(signal.reason)
    })

    // Simulate a long-running operation
    const id = setTimeout(() => resolve("Did Something"), 5000)
  })
}

Making your own abortable API is as simple as that. You can now use this function with an AbortController.

// Cancel the operation after 3 seconds
const signal = AbortSignal.timeout(3000)

doSomething(signal)
  .then(result => console.log(result))
  .catch(err => {
    if (err.name === "AbortError") {
      console.log("Operation was aborted")
    } else {
      throw err
    }
  })

Here is a simple function you can use to make abortable functions easier.

function makeAbortable(fn) {
  return signal => {
    return new Promise((resolve, reject) => {
      if (signal.aborted) {
        reject(signal.reason)
        return
      }

      signal.addEventListener("abort", () => {
        reject(signal.reason)
      })

      fn(resolve, reject, signal)
    })
  }
}

Conclusion

AbortController is a powerful API that goes beyond just canceling fetch requests. It can be used to clean up event listeners, create timeout signals, and even build your own abortable functions.