Photo by Jeremy Perkins on Unsplash
A gotcha with the Fetch API
It’s rare that I use the Fetch API directly these days but every time I do there’s one behaviour that I forget about... HTTP errors
When using the Fetch API, HTTP errors don’t result in an error being thrown. Let's look at what can go wrong when we make a network request.
Network errors
Network errors happen when the roundtrip between the browser and the server cannot be completed. Possible reasons for this include:
The client’s network connection drops
An incorrect URL is used
The server goes down and cannot respond
If a network error occurs the Fetch API will throw an error 👍
try {
const response = await fetch("https://myapi.com/user/1");
} catch (error) {
// FetchError: ...
}
HTTP errors
HTTP responses from the server with status codes beginning 4xx
and 5xx
represent errors. Common HTTP error status codes you might encounter are:
404
when a resource isn’t found403
when the client isn’t authorised to access the resource500
an unexpected failure happened on the server
If an HTTP error occurs the Fetch API will NOT throw an error 👎
try {
const response = await fetch("https://myapi.com/user/1");
// HTTP errors do not throw so you must parse the response
if (!response.ok) {
// We have a HTTP error
}
} catch (error) {
// We have a network error
}
I find this to be a surprising behaviour that has the potential for uncaught runtime errors to lean into your application. So how can we handle HTTP errors gracefully?
How to handle HTTP errors manually
I like to create a custom error class that contains a status code and message that gets thrown on an HTTP error.
Fetch responses always contain a status code. Whether or not you have an error message and how you get that message will depend on the response the server sends. In the example below let's assume that the error message is sent back as plain text in the response body.
// HttpError.ts
export class HttpError extends Error {
public readonly statusCode: number;
constructor(statusCode: number, message: string) {
super(`HTTP Error (${statusCode}): ${message}`);
this.name = "HttpError";
this.statusCode = statusCode;
}
}
// fetch.ts
import { HttpError } from "./HttpError";
try {
const response = await fetch("https://myapi.com/user/1");
if (!response.ok) {
await response.text().then(message => {
throw new HttpError(response.status, message);
});
}
// handle successful responses
}
But you should use a library
Handling HTTP errors manually with Fetch is a bit of a pain which is why I prefer to use a library. My library recommendations are:
Both of these libraries throw a real error when receiving an HTTP error.
Thanks for reading ⭐️