DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

Maxime
Maxime

Posted on

Chaining catch blocks with async/await

When you're working with an API within a large application, it can become tedious and repetitive to handle errors on a case-by-case basis.

Consider this API call wrapped into a function:

async function getRandomDog() {
  const response = await axios.get('https://dog.ceo/api/breeds/image/random');
  return response;
}

// Display a random dog image
getRandomDog().then(response => {
  const image = document.createElement("img");
  image.setAttribute("src", response.data.message);
  image.setAttribute("width", "400");
  document.body.appendChild(image);
});
Enter fullscreen mode Exit fullscreen mode

This works fine, but we should probably handle errors in case our API call fails:

// Display a random dog image
getRandomDog()
  .then(url => {
    const image = document.createElement("img");
    image.setAttribute("src", response.data.message);
    image.setAttribute("width", "400");
    document.body.appendChild(image);
  })
  .catch(error => {
    alert("Unable to find a dog :(");
  });
Enter fullscreen mode Exit fullscreen mode

We now tell the users if the API fails, but we'd also like to log the incident so that we know there's a problem with our app. In a real application, we'd have access to a logging service, but for simplicity's sake we'll use console.log:

async function getRandomDog() {
  const response = await axios
    .get("https://dog.ceo/api/breeds/image/random")
    .catch(error => {
      console.log(error);
      return error;
    });
  return response.data.message;
}
Enter fullscreen mode Exit fullscreen mode

Assuming that the logError works properly, we should now be notified when our API fails. However, our users won't see the alert anymore because we've already caught the error. That's not a great user experience!

Our first instinct might be to try chaining catch blocks, but that won't work:

const data = await axios
  .get("https://dog.ceo/api/breeds/image/random")
  .catch(error => {
    console.log(error);
  })
  .catch(error => {
    // This block will never get called
    alert("Something went wrong");
  });
Enter fullscreen mode Exit fullscreen mode

My favorite solution to this is to throw the error again to trigger the new catch block in our code:

const data = await axios.get("https://fake.api.com")
  .catch(error => {
    console.log(error);
    throw error;
  })
  .catch(error => {
    alert("Something went wrong");
  });
Enter fullscreen mode Exit fullscreen mode

Now, when our API call fails, we'll log the error to our reporting system and notify the user. Win win!


If we have multiple endpoints, we could also go one step further and centralize the error reporting. For instance:

function handleAPIError(error) {
  const { status } = error.response;

  switch (status) {
    case 400:
      console.log("Error: invalid request");
      break;
    case 401:
      console.log("Error: not authenticated");
      break;
    case 500:
      console.log("Error: server problems");
      break;
  }

  throw error;
}

async function getRandomDog() {
  const response = await axios
    .get("https://dog.ceo/api/breeds/image/random")
    .catch(handleAPIError);
  return response;
}

async function getRandomCat() {
  const response = await axios
    .get("https://api.thecatapi.com/v1/images/search")
    .catch(handleAPIError);
  return response;
}
Enter fullscreen mode Exit fullscreen mode

We've now implemented an error handler that can be reused across our API helpers. It allows the errors to filter through to the frontend so that we can display the proper error message to our users.

Here's a Pen with infinite cats, dogs, and errors:


Thank you for reading! Let me know what you think, and I'd love to know how you handle errors in your apps.

Top comments (0)

An Animated Guide to Node.js Event Lop

Node.js doesn’t stop from running other operations because of Libuv, a C++ library responsible for the event loop and asynchronously handling tasks such as network requests, DNS resolution, file system operations, data encryption, etc.

What happens under the hood when Node.js works on tasks such as database queries? We will explore it by following this piece of code step by step.