DEV Community

Cover image for Fetch API & AbortController
Amin
Amin

Posted on • Updated on

Fetch API & AbortController

If you are used to fetching data using the Fetch API in React (or Preact), you should be pretty familiar with this kind of code.

const Home = () => {
  const [users, setUsers] = useState([]);
  const [error, setError] = useState("");

  useEffect(() => {
    const endpoint = "https://jsonplaceholder.typicode.com/users";

    fetch(endpoint).then(response => {
      return response.json();
    }).then(newUsers => {
      setUsers(newUsers);
    }).catch(({message}) => {
      setError(message);
    });
  }, []);

  if (error) {
    return (
      <div>
        <h2>Error</h2>
        <p>{error}</p>
      </div>
    );
  }

  return (
    <ul>
      {users.map(({username}, key) => (
        <li key={key}>{username}</li>
      ))}
    </ul>
  );
};
Enter fullscreen mode Exit fullscreen mode

But what happens when you are on a very slow connection all of a sudden? Maybe the network is not responding right away so you start going to another page.

And precisely at this very moment, on another page you start requesting something else with the same pattern and you find yourself with two requests, fighting each others to get the network juice that is already low.

So you start naturally asking yourself: is my network low? So you test trying to reach another page with the exact same pattern and now you have three connections to a server using the bandwidth of a network that is really slow.

This is a problem that can be easily solved by using an AbortController.

const Home = () => {
  const [users, setUsers] = useState([]);
  const [error, setError] = useState("");

  useEffect(() => {
    const endpoint = "https://jsonplaceholder.typicode.com/users";

    // Instanciation of our controller
    const controller = new AbortController();

    // Attaching the signal to the request
    fetch(endpoint, {signal: controller.signal}).then(response => {
      return response.json();
    }).then(newUsers => {
      setUsers(newUsers);
    }).catch(({message}) => {
      setError(message);
    });

    // Canceling the request when the component is destroyed
    return () => controller.abort();
  }, []);

  if (error) {
    return (
      <div>
        <h2>Error</h2>
        <p>{error}</p>
      </div>
    );
  }

  return (
    <ul>
      {users.map(({username}, key) => (
        <li key={key}>{username}</li>
      ))}
    </ul>
  );
};
Enter fullscreen mode Exit fullscreen mode

I added a comment above the new additions. These three lines are enough to prevent running requests on the background that could flood the network unnecessarily.

Now, when the user go to another page, the cleanup function will be run and the abort controller will stop the request, thus saving some precious bandwidth for another request that will (hopefully) succeed this time.

Actually, the same concept applies to Vue.js and you can call the controller in the destroyed lifecycle method.

<template>
  <div v-if="error">
    <h2>Error</h2>
    <p>{{ error }}</p>
  </div>
  <ul v-else>
    <li v-for="(user, key) in users" :key="key">{{ user.username }}</li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      endpoint: "https://jsonplaceholder.typicode.com/users",
      controller: new AbortController(),
      error: "",
      users: []
    };
  },

  created() {
    fetch(this.endpoint, {signal: this.controller.signal}).then(response => {
      return response.json();
    }).then(newUsers => {
      this.users = newUsers;
    }).catch(({message}) => {
      this.error = message;
    });
  },

  destroyed() {
    this.controller.abort();
  }
};
</script>
Enter fullscreen mode Exit fullscreen mode

Discussion (0)