How to handle 4xx and 5xx errors in Javascript fetch()
Tags: react, javascript, angular, fetch
Published: 2021-03-27 12:27:04

Many modern javascript applications make use of the in-built [`fetch()`](https://github.com/github/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:

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1616838030326/Dhvq2rKBQ.png)

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:
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1616838809813/ug-tXIt8Y.png)

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](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch), `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:
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1616843574446/IRjkhdr0o.png)
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 
![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1616843805089/rdjYOvXOe.png)
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.

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1616844709680/b8GVrxYG3.png)

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.