How to handle 4xx and 5xx errors in Javascript fetch()
Many modern javascript applications make use of the in-built fetch()
API. This API takes care of most of the HTTP stuff and hence reduces the overhead of an external library like Axios or jQuery in many cases.
fetch
makes use of Promise
instead of callbacks to make things easier for the developers. For the sake of explanation, Let's fetch my github profile as an example and log to console. Typically, our fetch
usage would look like this:
function fetchProfile(profileURL) {
fetch(profileURL)
.then(res => res.json())
.then(userData => console.log('Response: ', userData))
}
fetchProfile('https://api.github.com/users/ahmedtabrez')
// This logs my profile in JSON format
This should work for a happy flow. Let's do some error handling now.
function fetchProfile(profileURL) {
fetch(profileURL)
.then(res => res.json())
.then(userData => console.log('Success: ', userData))
.catch(error => console.error('Error: ', error))
}
fetchProfile('https://non.sense.url/')
And this is what gets logged to my console:
Fair enough so far. Now let's try fetching my profile with a deliberate typo (let's add a hyphen at the end)
fetchProfile('https://api.github.com/users/ahmedtabrez-')
And here's what is logged:
Wait a minute, why did it log Success: <object>
? Wasn't it supposed to log Error: <object>
according to our code just like it did for https://non.sense.url/
?
Well, that's the caveat that makes using fetch
slightly difficult.
According to the docs, fetch
actually throws an exception only if there is a network failure or if something prevented the request from completing. It doesn't throw an exception for 4xx or 5xx responses
So in our case, fetchProfile('https://non.sense.url/')
entered the catch
block as expected because there was a network failure. There was actually no host at https://non.sense.url/
. But when we did fetchProfile('https://api.github.com/users/ahmedtabrez-')
, there was no network error. The URL we called took the request and responded with a response code. The request was complete and hence fetch
did not consider that as an error.
If you have been using APIs like jQuery.ajax
, you might expect the 4xx and 5xx errors in the catch
block too. So let's write a wrapper function as a workaround for this.
To begin with:
function myFetch(...args) {
return fetch(...args)
}
To handle the 4xx and 5xx responses, the fetch
API, fortunately, provides us with a flag ok
in the response object. We can make use of this flag to our advantage.
function myFetch(...args) {
return fetch(...args)
.then(res => {
if (res.ok) {
return res
} else {
throw new Error('An error occurred')
}
})
}
That would be enough. Now let's update our fetchProfile
function and try again.
function fetchProfile(profileURL) {
myFetch(profileURL)
.then(res => res.json())
.then(userData => console.log('Success: ', userData))
.catch(error => console.error('Error: ', error))
}
fetchProfile('https://api.github.com/users/ahmedtabrez-')
And this is what the console looks like now:
As expected, the 404 response causes our fetchProfile
to enter into the catch block.
A quick enhancement
In the network log in developer tools, The response body looks like
Let's say we want the server error response body within the catch block for handling the user experience for example by showing an error popup. But our implementation so far is only capable of giving us a generic error message An error occurred
. To make this happen, we will make use of Promise.reject
in place of throw
like so:
function myFetch(...args) {
return fetch(...args)
.then(res => {
if (res.ok) {
return res
} else {
// Assume our 4xx/5xx responses are always JSON objects for the sake of simplicity
return res.json().then(json => Promise.reject(json))
}
})
}
Now let's see what fetchProfile('https://api.github.com/users/ahmedtabrez-')
logs in console.
Now we have the error response body available at our catch block and we can make use of it as per our requirements.
Conclusion
There are tons of other implementations available over the internet and they could be more robust than this implementation. But as far as I was able to search, I could not find any implementation which gives us the response body in the catch
block. That's what motivated me to write this post.
Let me know in the comments below if you feel like giving me any feedback. Also, let me know if you want me to write on a topic. I would love to explore.