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);
});
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 :(");
});
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;
}
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");
});
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");
});
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;
}
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)