This article will show you how to cancel fetch requests in React and TypeScript applications

A React component

Here is a React component that takes some data and renders it using a Web API

export function App() {
  const [status, setStatus] = React.useState<
    "loading" | "loaded" | "cancelled"> ("loading");
  const [data, setData] = React.useState<Character | undefined> (undefined);

  React.useEffect(() = > {
    getCharacter(1).then((character) = > {
      setData(character);
      setStatus("loaded"); }); } []);if (status === "loading") {
    return (
      <div>
        <div>loading ...</div>
        <button>Cancel</button>
      </div>
    );
  }
  if (status === "cancelled") {
    return <div>Cancelled</div>;
  }

  return <div>{data && <h3>{data.name}</h3>}</div>;
}
Copy the code

The network data is retrieved by the getCharacter function in useEffect and then set to data using useState

At the same time, we define a status variable to store the status of the request data (‘ requested ‘/’ request completed ‘/’ Request canceled ‘/). Note that the interface renders a Cancel button when the state is’ requesting ‘

When the “Cancel” button is clicked, we can cancel the fetch request

Next, let’s see what’s inside the getCharacter function

async function getCharacter(id: number) {
  const response = await fetch(`https://swapi.dev/api/people/${id}/ `);
  const data = await response.json();
  assertIsCharacter(data);
  return data;
}
Copy the code

Fetch calls a free interface in the code and returns the requested data

The type of Character below

type Character = {
  name: string;
};
Copy the code

Next, look at the assertIsCharacter function

function assertIsCharacter(data: any) :asserts data is Character {
  if(! ("name" in data)) {
    throw new Error("Not character"); }}Copy the code

Set the data type to Character using TypeScript type assertions.

useAbortControllerTo cancel the fetch

1AbortController is a feature in the latest version of JavaScript that appeared after FETCH was implemented. The even better news is that all modern browsers support it.

AbortController contains an abort method. It also contains a signal property that can be passed to the fetch. When abortController.abort is called, the FETCH request is canceled.

Let’s use AbortController and its signal property in the fetch request for getCharacter:

function getCharacter(id: number) {
  // Get the AbortController instance
  const controller = new AbortController();
  // Get the signal attribute
  const signal = controller.signal;
  const promise = new Promise(async (resolve) => {
    const response = await fetch(`https://swapi.dev/api/people/${id}/ `, {
      method: "get".// Set signal as one of the fetch parameters
      signal,
    });
    const data = await response.json();
    assertIsCharacter(data);
    resolve(data);
  });
  // Set a cancel function
  promise.cancel = () = > controller.abort();
  return promise;
}
Copy the code

We removed the async keyword from the getCharacter function and wrapped the existing code in a new Promise. When the data is requested, we use resolve to throw it out. We added a cancel method to the Promise that calls abortController.abort.

The Promise containing the cancel method is returned from the getCharacter so that we can use it in our business code to cancel the request.

Save the code to view the interface effect, found a type error:

// 💥 - Property 'cancel' does not exist on type 'Promise<unknown>'
promise.cancel = () = > controller.abort();
Copy the code

Let’s create a type for the Promise’s Cancel method

interface PromiseWithCancel<T> extends Promise<T> {
  cancel: () = > void;
}
// Use however to use type assertion to resolve the previous type error
function getCharacter(id: number) {... (promiseas PromiseWithCancel<Character>).cancel = () = > controller.abort();
  return promise as PromiseWithCancel<Character>;
}
Copy the code

Used in the React componentgetCharacter

We’ll store the promise returned by getCharacter in a state variable named Query.

export function App() {
  const [status, setStatus] = React.useState<"loading" | "loaded" | "cancelled"> ("loading");
  const [data, setData] = React.useState<Character | undefined> (undefined);
  const [query, setQuery] = React.useState<PromiseWithCancel<Character> | undefined> (undefined);
  React.useEffect(() = > {
    const q = getCharacter(1);
    setQuery(q);
    q.then((character) = > {
      setData(character);
      setStatus("loaded"); }); } []); .Copy the code

Now, when the cancel button is clicked, we call the cancle method in the Promise

<button
  onClick={() = >{ query? .cancel(); setStatus("cancelled");
  }}
>
  Cancel
</button>
Copy the code

After clicking the Cancel button, we see that the ‘Cancelled’ copy has been rendered

I checked the web request with Google Developer tools and found that the request had also been canceled

Isn’t it great? 😊

Catch a cancel request error

Let’s look at the console again. When the request is cancelled, an error is thrown

We can wrap the request with a try catch to catch errors

const promise = new Promise(async (resolve) => {
  try {
    const response = await fetch(`https://swapi.dev/api/people/${id}/ `, {
      method: "get",
      signal,
    });
    const data = await response.json();
    assertIsCharacter(data);
    resolve(data);
  } catch (ex: unknown) {
    if (isAbortError(ex)) {
      console.log(ex.message); }}});Copy the code

Functions of type isAbortError are as follows

function isAbortError(error: any) :error is DOMException {
  if (error && error.name === "AbortError") {
    return true;
  }
  return false;
}
Copy the code

Now when we click the Cancel button again, we will receive a message on the console instead of an error

conclusion

You can pass the signal property in AbortController to fetch. Abortcontroller.abort can then be called to abort the request.

Canceling the fetch raises an error, but it can be caught using a try catch.

Reference: www.carlrippon.com/cancelling-…

The last

Do you know of any other ways to interrupt a request? Feel free to leave your thoughts in the comments!

Feel the harvest of friends welcome to praise, pay attention to a wave!

The articles

React Build series

  1. How to build the enterprise front-end development specification 🛠
  2. React Build integration Webpack5/React17
  3. React Build integrated CSS/Less/Sass/Antd
  4. React Build integrated image/font
  5. React Build integration with Redux/Typescript
  6. React Build uses redux-thunk to implement asynchronous Redux operations
  7. React Build integrates React-Router/Antd Menu to implement route permissions