This article demonstrates some error handling approaches when working with the Fetch API. The Fetch API lets you make a request to a remote network resource. When you make a remote network call, your web page becomes subject to a variety of potential network errors.
The following sections describe potential errors and describe how to write code that provides a sensible level of functionality that is resilient to errors and unexpected network conditions. Resilient code keeps your users happy and maintains a standard level of service for your website.
Anticipate potential network errors
This section describes a scenario in which the user creates a new video named
"My Travels.mp4"
and then attempts to upload the video to a video-sharing website.
When working with Fetch, it's easy to consider the happy path where the user successfully uploads the video. However, there are other paths that are not as smooth, but for which web developers must plan. Such (unhappy) paths can happen due to user error, through unexpected environmental conditions, or because of a bug on the video-sharing website.
Examples of user errors
- The user uploads an image file (such as JPEG) instead of a video file.
- The user begins uploading the wrong video file. Then, part way through the upload, the user specifies the correct video file for upload.
- The user accidentally clicks "Cancel upload" while the video is uploading.
Examples of environmental changes
- The internet connection goes offline while the video is uploading.
- The browser restarts while the video is uploading.
- The servers for the video-sharing website restart while the video is uploading.
Examples of errors with the video-sharing website
- The video-sharing website cannot handle a filename with a space. Instead of
"My Travels.mp4"
, it expects a name such as"My_Travels.mp4"
or"MyTravels.mp4"
. - The video-sharing website cannot upload a video that exceeds the maximum acceptable file size.
- The video-sharing website does not support the video codec in the uploaded video.
These examples can and do happen in the real world. You may have encountered such examples in the past! Let's pick one example from each of the previous categories, and discuss the following points:
- What is the default behavior if the video-sharing service cannot handle the given example?
- What does the user expect to happen in the example?
- How can we improve the process?
Handle errors with the Fetch API
Note that the following code examples use top-level await
(browser support) because this feature can simplify your code.
When the Fetch API throws errors
This example uses a try
/catch
block statement to catch any errors thrown within the try
block. For example, if the Fetch API cannot fetch the specified resource, then an error is thrown. Within a catch
block like this, take care to provide a meaningful user experience. If a spinner, a common user interface that represents some sort of progress, is shown to the user, then you could take the following actions within a catch
block:
- Remove the spinner from the page.
- Provide helpful messaging that explains what went wrong, and what options the user can take.
- Based on the available options, present a "Try again" button to the user.
- Behind the scenes, send the details of the error to your error-tracking service, or to the back-end. This action logs the error so it can be diagnosed at a later stage.
try {
const response = await fetch('https://website');
} catch (error) {
// TypeError: Failed to fetch
console.log('There was an error', error);
}
At a later stage, while you diagnose the error that you logged, you can write a test case to catch such an error before your users are aware something is wrong. Depending on the error, the test could be a unit, integration, or acceptance test.
When the network status code represents an error
This code example makes a request to an HTTP testing service that always responds with the HTTP status code 429 Too Many Requests
. Interestingly, the response does not reach the catch
block. A 404 status, amongst certain other status codes, does not return a network error but instead resolves normally.
To check that the HTTP status code was successful, you can use any of the following options:
- Use the
Response.ok
property to determine whether the status code was in the range from200
to299
. - Use the
Response.status
property to determine whether the response was successful. - Use any other metadata, such as
Response.headers
, to assess whether the response was successful.
let response;
try {
response = await fetch('https://httpbin.org/status/429');
} catch (error) {
console.log('There was an error', error);
}
// Uses the 'optional chaining' operator
if (response?.ok) {
console.log('Use the response here!');
} else {
console.log(`HTTP Response Code: ${response?.status}`)
}
The best practice is to work with people in your organization and team to understand potential HTTP response status codes. Backend developers, developer operations, and service engineers can sometimes provide unique insight into possible edge cases that you might not anticipate.
When there is an error parsing the network response
This code example demonstrates another type of error that can arise with parsing a response body. The Response
interface offers convenient methods to parse different types of data, such as text or JSON. In the following code, a network request is made to an HTTP testing service that returns an HTML string as the response body. However, an attempt is made to parse the response body as JSON, throwing an error.
let json;
try {
const response = await fetch('https://httpbin.org/html');
json = await response.json();
} catch (error) {
if (error instanceof SyntaxError) {
// Unexpected token < in JSON
console.log('There was a SyntaxError', error);
} else {
console.log('There was an error', error);
}
}
if (json) {
console.log('Use the JSON here!', json);
}
You must prepare your code to take in a variety of response formats, and verify that an unexpected response doesn't break the web page for the user.
Consider the following scenario: You have a remote resource that returns a valid JSON response, and it is parsed successfully with the Response.json()
method. It may happen that the service goes down. Once down, a 500 Internal Server Error
is returned. If appropriate error-handling techniques are not used during the parsing of JSON, this could break the page for the user because an unhandled error is thrown.
When the network request must be canceled before it completes
This code example uses an AbortController
to cancel an in-flight request. An in-flight request is a network request that has started but has not completed.
The scenarios where you may need to cancel an in-flight request can vary, but it ultimately depends on your use case and environment. The following code demonstrates how to pass an AbortSignal
to the Fetch API. The AbortSignal
is attached to an AbortController
, and the AbortController
includes an abort()
method, which signifies to the browser that the network request should be canceled.
const controller = new AbortController();
const signal = controller.signal;
// Cancel the fetch request in 500ms
setTimeout(() => controller.abort(), 500);
try {
const url = 'https://httpbin.org/delay/1';
const response = await fetch(url, { signal });
console.log(response);
} catch (error) {
// DOMException: The user aborted a request.
console.log('Error: ', error)
}
Conclusion
One important aspect of handling errors is to define the various parts that can go wrong. For each scenario, make sure you have an appropriate fallback in place for the user. With regards to a fetch request, ask yourself questions such as:
- What happens if the target server goes down?
- What happens if Fetch receives an unexpected response?
- What happens if the user's internet connection fails?
Depending on the complexity of your web page, you can also sketch out a flowchart which describes the functionality and user interface for different scenarios.