How to implement a request timeout using the Promise.race method

Fetching data from a REST API is common in web applications today, especially with the rise of single-page applications. When fetching, the frontend is decoupled from the backend, and the frontend fetches the data from the backend (usually in JSON format).

Many things could go wrong when making HTTP requests to the backend, and the proper handling of potential errors is essential to a good user experience.

One of the things that we need to keep in mind when fetching data from the backend is that the server could take a long time to respond; worse yet, it may not respond at all due to a connection timeout. As a result, our request will be left hanging.

Ideally, we need a timeout mechanism that will allow us to take some appropriate action if the server takes more time to respond to our request than we are comfortable with.

In JavaScript, one way to implement this timeout mechanism is to use the Promise.race() method.

From MDN - Promise.race: The Promise.race() method returns a promise that fulfills or rejects as soon as one of the promises in an iterable fulfills or rejects with the value or reason from that promise.

How to use the Promise.race() method to implement timeout

To implement the timeout feature, we can pass an array of two promises to the Promise.race() method: one promise will be related to our actual request to the backend, and the second promise will be a promise that will be rejected after a specific amount of time.

If the promise related to our HTTP request is not settled before the timeout promise, our request will be considered to have timed out. Then, we can take the appropriate action (like sending the request again or sending an error message to the user).

Code

For example, we will create a function that will act as a fake API that takes too long to respond.

// function to mock an API
// that takes at-least 3 seconds
// to send a response
function fakeAPI() {
return new Promise(resolve => {
setTimeout(() => {
resolve("Hello world");
}, 3000);
});
}
function timeoutAfter(seconds) {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("request timed-out"));
}, seconds * 1000);
});
}
async function requestWithTimeout() {
try {
const response = await Promise.race(
[timeoutAfter(2), fakeAPI()]
);
console.log(response);
} catch (error) {
console.log(error.message);
}
}
requestWithTimeout();

In the code example above, we set the timeout period to be 2 seconds, and the promise returned by the fakeAPI function is resolved after 3 seconds. So, in this case, you will always see “request timed-out” printed on the console.

If we were to increase the time period of a timeout to, let’s say, 4 seconds, we will see “Hello world” printed on the console instead.

Although this is a contrived example in the real world, if we are using the fetch API to send the request to the backend, instead of calling the fakeAPI function, we will call the fetch function, passing in the URL of the API:

async function requestWithTimeout() {
    // code
    const response = await Promise.race(
      [timeoutAfter(2), fetch(/* api url*/)]
    );

    // code
}

Free Resources