Handling bad responses with Fetch
If you’ve ever worked with APIs or made HTTP requests in JavaScript, you’re probably familiar with the built-in Fetch API. While fetch is a great tool for making HTTP requests, it can sometimes be a bit cumbersome to work with, especially when you need to handle errors or configure your requests with custom headers and options.
In this post, we’ll take a closer look at the default behavior of fetch, explain how to use the ok response to check for successful requests, and discuss how to handle bad responses that may include additional error information in the response body. We’ll also introduce a small library that wraps fetch and simplifies error handling, enabling easier access to any additional error information that may be included in the response.
The Default Behavior of fetch
By default, fetch does not throw errors or reject promises on 4xx and 5xx HTTP status codes. Instead, it resolves the promise with the Response object, regardless of whether the request was successful or not. This means that you need to manually check the ok response property to determine whether the request was successful or not.
Here’s an example:
fetch('https://run.mocky.io/v3/7d8c436e-d1dc-4857-b0ac-7e3e8047aef8')
.then((response) => {
if (!response.ok) {
throw new Error('Something went horribly wrong 💩')
}
return response.json()
})
.then((data) => console.log(data))
.catch((error) => console.error(error))
In this example, we’re making a request to a mock API (provided by Mocky) that sends back a bad response. We’re using the ok response property to check whether the response was successful or not. If the response was not successful, we’re throwing an error that will be caught by the catch block.
Why this isn’t the best solution
While checking the ok
property is an effective way to identify unsuccessful requests, this method has a significant limitation: it doesn’t allow you to access potentially useful error information included in the response body. Many APIs will send back a JSON response along with a bad response that may contain more information about what went wrong. For example, an API might return a 400 error with a JSON response that includes an error message saying what went wrong and why.
If you run the above example and use your developer tools to inspect the response from the API you will see that the response also included some JSON:-
But we didn’t get that response in our code and so were not able to do
anything meaningful with it. We see our thrown error message logged to the
console, but in our code we have no access to the JSON that the server also
sent.
To get that JSON response in our code we can add in an additional check to see
if the response included some JSON.
Check if the bad response includes some JSON:
fetch('https://run.mocky.io/v3/7d8c436e-d1dc-4857-b0ac-7e3e8047aef8')
.then((response) => {
if (!response.ok) {
// check if there was JSON
const contentType = response.headers.get('Content-Type')
if (contentType && contentType.includes('application/json')) {
// return a rejected Promise that includes the JSON
return response.json().then((json) => Promise.reject(json))
}
// no JSON, just throw an error
throw new Error('Something went horribly wrong 💩')
}
return response.json()
})
.then((data) => console.log(data))
.catch((error) => {
console.error('Here we caught the rejection with a JSON response', error)
})
Now in our catch we get the bad response and the JSON payload:-
Build a library to wrap Fetch
This is much better. But what if we want to make other HTTP requests too? We can make ourselves a small library that wraps Fetch with the above logic and exposes a set of methods for making POST, PUT, DELETE and GET requests which then becomes as simple as:-
// create an instance of the library
const api = http('https://run.mocky.io/v3/7d8c436e-d1dc-4857-b0ac-7e3e8047aef8')
api
.get('/posts')
.then(({ data }) => {
console.log('✅ Got a good response:-\n', data)
})
.catch(({ error, data }) => {
console.error('❌ Got a bad response:-\n', error, data)
})
This will likely be familiar to use if you have ever used a library such as Axios So is a pretty neat solution if you don’t need all the features of a Axios, but do need a little more than Fetch offers “out of the box”.