Error handing when using the vanilla JS fetch() method with async and await
On Friday, we looked at how to use the async
and await
operators with the fetch()
method.
At the end of the article, I mentioned…
The current setup will break very ungracefully if there’s an error with the API response.
Today, we’re going to look at how to handle errors when using the fetch()
method with async
and await
.
The problem
On Friday, I showed you how you can call the json()
method directly on the postResp
and authorResp
variables, because they returned response gets assigned to them instead of the Promise that fetch()
returns.
var getPost = async function () {
// Get the post data
var postResp = await fetch('https://jsonplaceholder.typicode.com/postses/5');
var post = await postResp.json();
// Get the author
var authorResp = await fetch('https://jnosplaceholder.typicode.com/users/' + post.userId);
var author = await authorResp.json();
console.log(post, author);
};
getPost();
However, if the API doesn’t return a response for some reason—a mistyped URL, an error on the server, and so on—the json()
method won’t have anything to run on.
Using then()
and catch()
The most obvious solution is to use the method specifically designed to handle catching errors with Promises: catch()
.
If the response is successful, you can return
the response.json()
. If not, you’ll use catch()
to log an error and post
and author
will be set to undefined
.
You can check to see if post
and author
have values before moving on in your script.
var getPost = async function () {
// Get the post data
var post = await fetch('https://jsonplaceholder.typicode.com/posts/5').then(function (response) {
return response.json();
}).catch(function (err) {
console.warn('Could not find a post');
});
// If there's no post, warn
if (!post) return;
var author = await fetch('https://jsonplaceholder.typicode.com/users/' + post.userId).then(function (response) {
return response.json();
}).catch(function (err) {
console.warn('Could not find an author');
});
// If there's no author, warn
if (!author) return;
console.log(post, author);
};
getPost();
The fetch()
method only throws an error if the call does not resolve. A response that returns a status code of 400
or 500
would still be considered a “success”.
We can also check in our then()
methods to make sure the response.ok
property is true
, and force an error if it’s not.
var getPost = async function () {
// Get the post data
var post = await fetch('https://jsonplaceholder.typicode.com/posts/5').then(function (response) {
if (response.ok) {
return response.json();
} else {
return Promise.reject(response);
}
}).catch(function (err) {
console.warn('Could not find a post');
});
// If there's no post, warn
if (!post) return;
var author = await fetch('https://jsonplaceholder.typicode.com/users/' + post.userId).then(function (response) {
if (response.ok) {
return response.json();
} else {
return Promise.reject(response);
}
}).catch(function (err) {
console.warn('Could not find an author');
});
// If there's no author, warn
if (!author) return;
console.log(post, author);
};
getPost();
Here’s a demo with the script catching errors, and here’s one with it working correctly.
So, this works. But as you can see, it’s absurdly verbose.
A more compact way to handle errors
Major kudos to Steve Griffith for showing me a more concise way to handle errors when using fetch()
with async
and await
.
The trick starts by wrapping our fetch()
method in parentheses (()
), and calling the catch()
method directly on it. To keep our code DRY, we’ll pass a named handleError()
callback function into the catch()
method.
var getPost = async function () {
// Get the post data
var post = await (fetch('https://jsonplaceholder.typicode.com/posts/5').catch(handleError));
// Get the author
var author = await (fetch('https://jsonplaceholder.typicode.com/users/' + post.userId).catch(handleError));
console.log(post, author);
};
getPost();
Inside the handleError()
method, we’ll return a new Response()
object. In the object, we’ll stringify an object with an error code
and message
.
var handleError = function (err) {
console.warn(err);
return new Response(JSON.stringify({
code: 400,
message: 'Stupid network Error'
}));
};
Next, we prefix fetch()
with await
inside the parentheses, and attach our json()
method outside of them.
If the fetch()
call is successful, it will automatically return a stringified response object to parse. If not, our handleError()
method will create and return one.
Either way, the json()
method has a stringified object to parse so no errors will be thrown.
var getPost = async function () {
// Get the post data
var post = await (await fetch('https://jsonplaceholder.typicode.com/posts/5').catch(handleError)).json();
// Get the author
var author = await (await fetch('https://jsonplaceholder.typicode.com/users/' + post.userId).catch(handleError)).json();
console.log(post, author);
};
getPost();
Finally, after each call, we’ll check to see if the returned data has a code
property with a value of 400
.
If it does, there was an error and we won’t more forward. Otherwise, we’re good to go.
var getPost = async function () {
// Get the post data
var post = await (await fetch('https://jsonplaceholder.typicode.com/posts/5').catch(handleError)).json();
if (post.code && post.code === 400) return;
// Get the author
var author = await (await fetch('https://jsonplaceholder.typicode.com/users/' + post.userId).catch(handleError)).json();
if (author.code && author.code === 400) return;
console.log(post, author);
};
getPost();
Here’s a demo with successful API calls, and here’s one with errors.
The one hiccup here is that if the method returned successfully but was not ok
, this approach won’t catch that.
Which approach should you use?
Personally, I still use the traditional then()
and catch()
approach with returned fetch()
methods.
I find the way it reads—first do this, then do this, then do that—easier to understand and wrap my head around.
I also think it offers more straightforward error handling and has better backwards compatibility without having to transpile code. It can be polyfilled if needed.
I do like that async
and await
read similar to synchronous functions, but I also don’t find then()
and catch()
any harder to read.