DEV Community

loading...

Cancel Fetch with AbortController

beejluig profile image BJ Cantlupe Originally published at bjcant.dev on ・4 min read

If you are like me, you have wondered if there is a way to cancel a fetch request. Well, there is good news: most modern browsers now support the AbortController API, which we can use to do just that! Let’s see how it works.

AbortController is a standalone object that can interface with the fetch method. The API for AbortController is pretty simple. We can instantiate a new controller with the constructor:

const controller = new AbortController();

The controller instance has just one property, controller.signal, and one method, controller.abort(). The signal property is an object with a boolean aborted property and an abort event listener. Try this out in the console.

// check the aborted status
controller.signal.aborted
//=> false

// setup 'abort' event listener
controller.signal.onabort = () => console.log('Aborted!');

controller.abort()
// logs: 'Aborted!'

controller.signal.aborted
//=> true

First, we check the read-only aborted property, which is false by default. Calling controller.abort() flips that value to true with no way to flip it back. Once an instance of AbortController is used, we need to create a new instance to reset the value.

How does this object interface with fetch? We can pass the signal as a fetch option like so:

const controller = new AbortController();

fetch(url, { signal: controller.signal })

When we pass a signal option to fetch, it creates a listener for the abort event and will throw an error if controller.abort() is called during the DOM request or while reading the request body.

Now let’s see a working example.

We have a button that fetches a large image and sets it as the background. The fetch button becomes an abort button while the request is being made.

To see this in action, you may need to double-click pretty quickly, depending on your internet speed. The browser may cache the image after the first time it loads, so you may need to clear application storage if you want to try aborting the fetch multiple times.

Let’s walk through this code. We can start with an initial App template:

function App() {
  return (
    <div
      className="App">
      <nav>
        <button>Fetch image</button>
      </nav>
    </div>
  );
}

The idea here is to wire up the button to fetch the image, then set it as the background of the App container. Let’s see that:

function App() {
  const [url, setUrl] = useState(); const fetchData = () => { setUrl(); return fetch("./buildings.jpg") .then(r => r.blob()) .then(blob => setUrl(URL.createObjectURL(blob))) };
  return (
    <div
      className="App"
      style={{backgroundImage: `url(${url})`}} >
      <nav>
        <button onClick={fetchData}>Fetch image</button> </nav>
    </div>
  );
}

So now the button is bound to the fetchData function, which creates a blob URL for the image and sets it to state, which in turn sets the background. Let’s add loading and error states.

function App() {
  const [url, setUrl] = useState();
  const [loading, setLoading] = useState(false); const [error, setError] = useState(false);
  const fetchData = () => {
    setUrl();
    setError(false); setLoading(true); return fetch("./buildings.jpg")
      .then(r => r.blob())
      .then(blob => setUrl(URL.createObjectURL(blob)))
      .catch(e => setError(e.message)) .finally(() => setLoading(false)); };
  return (
    <div
      className="App"
      style={{backgroundImage: `url(${url})`}}
    >
      <nav>
        <button onClick={fetchData}>Fetch image</button>
      </nav>
      {loading && <div>Loading...</div>} {error && <div>{error}</div>} </div>
  );
}

From here, adding the abort functionality is pretty easy. We just need to add an AbortController, wire up the abort button and pass the signal to fetch!

let controller = new AbortController();const abort = () => controller.abort();
function App() {
  const [url, setUrl] = useState();
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);

  const fetchData = () => {
    controller = new AbortController(); setUrl();
    setError(false);
    setLoading(true);
    return fetch(
      "./buildings.jpg",
      { signal: controller.signal } ).then(r => r.blob())
      .then(blob => setUrl(URL.createObjectURL(blob)))
      .catch(e => setError(e.message))
      .finally(() => setLoading(false));
  };
  return (
    <div className="App" style={{ backgroundImage: `url(${url})` }}>
      <nav>
        {!loading && <button onClick={fetchData}>Fetch image</button>} {loading && <button onClick={abort}>Abort fetch</button>} </nav>
      {loading && <div>Loading...</div>}
      {error && <div>{error}</div>}
    </div>
  );
}

You may be wondering why the controller variable is initially declared outside of the component. Remember that the controller.abort() functionality is a one-time use. Defining the controller inside the component risks object reference issues, i.e. abort() could be referencing the incorrect AbortController instance, rendering it useless. We want a fresh controller setup before every fetch, but we also need to ensure that the the abort() method is referring to the correct controller!

Conclusion

It turns out that aborting fetch requests is pretty simple with AbortController! Although the live example is in React, the concepts apply for any framework. Keep in mind that this does not work for Internet Explorer, so be sure to consider browser support before using this on production apps. For more information about AbortController, check out this article by Jake Archibald.

Discussion (0)

pic
Editor guide