Using AbortController out in the wild
Tags: beginners, webdev, javascript, tutorial
Published: 2020-04-09 16:46:15

I recently had to cancel requests made by `fetch` in one the projects I was working on and had a chance to use `AbortController`. Now there are some really good resources to learn about `AbortController` like [this one](https://developers.google.com/web/updates/2017/09/abortable-fetch) by Jake Archibald, but very few showcase using it in a real life scenario. Usually the examples in these tutorials will have a button to make an api call and a second one to cancel it. Although that's a good example to get started with, but I can't think of a case where I might ever build some thing like that in a "real" project. So here's an example where you might actually use `AbortController` in a real life scenario.

Imagine a search bar, where you need to make the api call to fetch data as you type. Something like this:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0uf5pk15sme53a9er1ml.gif)

Now, you will definitely come across a situation where a promise resolves faster than a previous one and you will be left displaying stale data to the user. You can definitely use ol' reliable [debounce](https://www.geeksforgeeks.org/debouncing-in-javascript/) for this, but that still doesn't solve your issue all the time.

This is where `AbortController` comes to your rescue!!! If a new api call is made while the previous one hasn't resolved, you can cancel the previous one using `AbortController`.

If you wanna jump into the code straight away here's a [demo](https://codesandbox.io/s/abortcontroller-example-ew3o1), but if you wanna know more what's going on, you can follow the blog furthermore:

In the `index.html` file, we have our input field.
```
 <input
   class="search-field"
   type="text"
   id="search"
   placeholder="Search for a joke"
>
```

On every `keyup` event, this input field fires a call to fetch data from our api:

```
// variable to track whether or not we are fetching data
let isLoading = false;

// event listener for our input field 
searchEl.addEventListener("keyup", e => {
  fetchQuote(e.target.value);
});

// function to call api and fetch data based on our search query
async function fetchQuote(search) {
  try {
    isLoading = true;

    const response = await fetch(
      `https://api.chucknorris.io/jokes/search?query=${search}`,
      { signal }
    );

    const data = await response.json();

    const jokes = data.result.slice(0, 5);

    isLoading = false;
    renderJokes(jokes);
  } catch (err) {
    isLoading = false;
  }
}
```



Note that we have a `isLoading` variable to tell us whether or not we have a pending promise.

Now that the logic for calling our api is done, let's initialize our `AbortController`:

```
let abortController = new AbortController();
let signal = abortController.signal;
``` 

And now to actually cancel our api call inside the `fetchQuote` function you can add `abortController.abort()` function: 

```
async function fetchQuote(search) {
  try {
    // Cancel our api call if isLoading is true
    if (isLoading) {
      abortController.abort();
    }

    isLoading = true;

    // Pass the "signal" as a param to fetch
    const response = await fetch(
      `https://api.chucknorris.io/jokes/search?query=${search}`,
      { signal }
    );
    // rest of the function is same as above
  } catch(err) {
    isLoading = false;
  }
}
```

Now that aborted request is cancelled, it actually goes to our `catch` block. Since technically this isn't an error, we can bypass this by checking for abort errors:

```
catch(err) {
  // Only handle errors if it is not an "AbortError"
  if (err.name === 'AbortError') {
    console.log('Fetch aborted');
  } else {
    console.error('Uh oh, an error!', err);
  }
}
```

Now any request we make cancels the previous request if it hasn't resolved yet. 

But there is a catch, this doesn't work for subsequent requests and only works for the first request. For `AbortController` to work for all of our subsequent requests, we need to create a new one each time we abort a request. Which leaves us with the following:

```
async function fetchQuote(search) {
  try {
    if (isLoading) {
      // Cancel the request
      abortController.abort();

      // Create a new instance of abortController
      abortController = new AbortController();
      signal = abortController.signal;
    }

    isLoading = true;

    const response = await fetch(
      `https://api.chucknorris.io/jokes/search?query=${search}`,
      { signal }
    );

    const data = await response.json();

    const jokes = data.result.slice(0, 5);

    isLoading = false;
    renderJokes(jokes);
  } catch (err) {
    isLoading = false;
    if (err.name === 'AbortError') {
      console.log('Fetch aborted');
    } else {
      console.error('Uh oh, an error!', err);
    }
  }
}
```

And now we are finally able to successfully use abort-able `fetch` requests out in the wild:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6wbw7nbdfbufltssilw3.gif)