Why Fetch calls fail and how to handle it
When your page makes a request with the Fetch API, it becomes subject to anything that can go wrong between the browser and the remote server. The upload of a file such as "My Travels.mp4" to a video-sharing site is a useful example: it is easy to code for the happy path where the upload simply works, but real-world failures come in several categories and require deliberate handling.
Know what can go wrong
User errors are the first category. A user may select a JPEG instead of a video file, start uploading the wrong file and then change the selection midway, or accidentally hit "Cancel upload" during the transfer.
Environmental conditions form the second category. The internet connection can drop mid-upload, the browser can restart, or the video-sharing site's servers can restart while the upload is in progress.
Finally, the service itself can impose constraints that you must account for. The site may reject filenames containing spaces, refuse files larger than a maximum size, or fail to support the codec used in the video.
Each of these cases has a default behavior, an outcome the user expects, and a way you can improve the experience. For example, a service that rejects filenames with spaces will simply return an error response; the user expects their upload to succeed; and you can improve the situation by validating the filename before sending the request.
Handling errors in code
The examples below use top-level await, which simplifies the code, and rely on browser support for that feature.
Network failures that throw
A try/catch block catches errors thrown by the Fetch API itself, such as when the requested resource cannot be reached. When an error is caught while a spinner is visible, your catch block should remove the spinner, explain what went wrong, and offer next steps such as a "Try again" button. You should also send error details to your error-tracking service or back end so the problem can be diagnosed later. Once you know the error, write a test case—unit, integration, or acceptance, depending on the error—to catch it before your users do.
try {
const response = await fetch('https://example.com/videos');
// process the response
} catch (error) {
// remove spinner, show message and retry button
// log error details to your tracking service
}
HTTP status codes that are not network errors
Fetch resolves normally for many error status codes. A response with 429 Too Many Requests or a 404 status does not reach the catch block; it resolves as a normal response. To detect such failures, check the response metadata:
- Use
Response.okto see whether the status code is in the200to299range. - Use
Response.statusto inspect the exact status code. - Use other metadata such as
Response.headersto assess the outcome.
const response = await fetch('https://example.com/videos');
if (!response.ok) {
// Handle the error status code, e.g. 429 or 500
}
Work with your backend developers, DevOps, and service engineers to learn which HTTP status codes your service can actually return. They often know about edge cases you would not anticipate on your own.
Parsing failures
Errors can also occur when parsing a response body. The Response interface provides convenient methods such as Response.json() to parse different data types. If a service returns an HTML string but your code tries to parse it as JSON, an error is thrown.
try {
const response = await fetch('https://example.com/videos');
const data = await response.json();
} catch (error) {
// Handle parsing errors
}
Your code must tolerate a variety of response formats. A service that normally returns valid JSON can go down and respond with 500 Internal Server Error; without proper error handling during parsing, that unhandled error can break the page for the user.
Canceling in-flight requests
Sometimes you need to cancel a request that has started but not yet completed. Use an AbortController to do this. Pass an AbortSignal to the Fetch API; the signal is attached to the controller, and calling the controller's abort() method tells the browser to cancel the request.
const controller = new AbortController();
const signal = controller.signal;
const response = await fetch('https://example.com/videos', { signal });
// later, to cancel the request:
controller.abort();
A practical approach to resilience
Reliable error handling starts with enumerating the things that can go wrong, then planning a fallback for each scenario. Ask yourself what happens if the target server goes down, if Fetch receives an unexpected response, or if the user's internet connection fails. For complex pages, a flowchart describing the user interface and behavior for each failure path can help you stay consistent across scenarios.



