Cleaning up fetch requests with the Abort Controller Web API

3 min read · 1 year ago

Tobi OjoTobi Ojo
Cleaning up fetch requests with the Abort Controller Web API

Hello everyone, i just wanted to share a little something i learned this week, the Abort Controller API.

What is it and what does it do?

The AbortController API in JavaScript allows you to cancel ongoing asynchronous tasks, such as fetch requests, before they complete. It gives you the ability to stop asynchronous tasks that are still in progress, like a fetch request to an external API, before they finish. This can be helpful in a number of situations:

  1. Users make quick changes (e.g, in a search box) and previous requests are no longer relevant.
  2. You need to prevent memory or network overhead by stopping tasks that no longer serve a purpose.
  3. You want to ensure that certain processes (like fetching) don’t continue running after a component unmounts in a React app.

Now in this article, we are going to learn how we can solve all three above mentioned issues with the Abort Controller API.

A common scenario in react

Imagine a fetch request inside a useEffect hook in a React app. The hook depends on a query state, which updates each time the user types in an input field. This triggers the useEffect and starts a new fetch request.

However, this causes a few problems:

  1. A fetch request is triggered on every keystroke
  2. Without intervention, all requests run in parallel, creating multiple unnecessary requests, slowing down the app, and causing network overhead and race conditions.

The solution? The AbortController API

The key elements of the Abort Controller API include:

  • AbortController: The core object that creates and manages the abort signal.
javascript
1const controller = new AbortController();
  • AbortSignal: A signal produced by the controller, which is passed to functions like fetch to enable cancellation. This is passed as an argument to the fetch alongside the fetch URL.
javascript
1{ signal: controller.signal }
  • abort() method: This method on the AbortController triggers the cancellation, sending a signal to the operation that should be stopped. This is used as a cleanup function.
javascript
1function () {
2        controller.abort();
3      }

Now i will demonstrate using this in practice showing what our network tab looks like before and after using the AbortController.

Without the AbortController API

javascript
1useEffect(
2    function () {
3      async function fetchMovies() {
4        try {
5          setIsLoading(true);
6          setErrorMessage("");
7          const res = await fetch(
8            `http://www.omdbapi.com/?apikey=${KEY}&s=${query}`
9          );
10
11          if (!res.ok) throw new Error("Something went wrong");
12
13          const data = await res.json();
14
15          if (data.Response === "False") throw new Error("Movie not found!");
16          setMovies(data.Search);
17          setErrorMessage("");
18        } catch (err) {
19         setErrorMessage(err.message);
20        } finally {
21          setIsLoading(false);
22        }
23      }
24
25      if (query.length < 3) {
26        setMovies([]);
27        setErrorMessage("");
28        return;
29      }
30
31      fetchMovies();
32    },
33    [query]
34  );

here, we can see every keystroke runs a fetch request

Blog post image
fetch request on every keystroke

In this version, every time the user types, a new fetch request is initiated. This results in unnecessary requests for every partial query like s, sp, spi, etc., when the user is really searching for spiderman

With the AbortController API

javascript
1useEffect(
2    function () {
3      const controller = new AbortController();
4      async function fetchMovies() {
5        try {
6          setIsLoading(true);
7          setErrorMessage("");
8          const res = await fetch(
9            `http://www.omdbapi.com/?apikey=${KEY}&s=${query}`,
10            { signal: controller.signal }
11          );
12
13          if (!res.ok) throw new Error("Something went wrong");
14
15          const data = await res.json();
16
17          if (data.Response === "False") throw new Error("Movie not found!");
18          setMovies(data.Search);
19          setErrorMessage("");
20        } catch (err) {
21          if (err.name !== "AbortError") setErrorMessage(err.message);
22        } finally {
23          setIsLoading(false);
24        }
25      }
26
27      if (query.length < 3) {
28        setMovies([]);
29        setErrorMessage("");
30        return;
31      }
32
33      fetchMovies();
34
35      return function () {
36        controller.abort();
37      };
38    },
39    [query]
40  );

here, we can see only the latest request is fetched, the others are cancelled

Blog post image
only data from last keystroke is fetched

With the AbortController, we cancel any previous fetch request when the user makes a new input, preventing unnecessary data from being fetched. Only the latest request is processed, improving performance and avoiding race conditions.

Result

When we compare both versions:

  • Without AbortController: Each keystroke results in a fetch request, retrieving unnecessary data and consuming more resources.
  • With AbortController: Only the final query (e.g., “spiderman”) is fetched. The previous incomplete queries are aborted, preventing extra network overhead.

By using AbortController, you can efficiently manage fetch requests in a React app, ensuring only the relevant data is fetched and network resources aren’t wasted.

This was a small insight into how the AbortController API can clean up your async operations in JavaScript! Hope this helps. Thanks for reading!

WRITINGS
3/3