I have a web request that sometimes completes in .1 seconds, and sometimes completes in 20 seconds.
My solution is to send requests with a timeout. When the timeout triggers, I want to send another request.
However, I want to leave the previous request open, and race it against the new request.
I want to do this until one of the open requests completes, ending the race for all open requests.
I'm also open to suggestions for an alternative approach to this problem.
const timeoutLimit = 300;
const timeoutMessage = 'Response timed out';
/** Executes a request, retrying on timeouts, until it returns successfully. */
export default async function requestWithTimeout(request, count) {
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject(timeoutMessage), timeoutLimit);
});
const apiCall = new Promise((resolve, reject) => {
request
.catch((err) => reject(err))
.then((resp) => resolve(resp));
});
count += 1;
const result = await Promise.race([apiCall, timeout])
.catch((err) => {
console.log(err, count);
return (err === timeoutMessage) ? Promise.race([apiCall, requestWithTimeout(request, count)]) : err;
})
.then((val) => {
console.log('returned', count);
return val;
});
console.log(count, result);
return result;
}
Here is an example of the output from the console: print statements indicating timeout and response order
I always get things printed in this order:
Response timed out 1
Response timed out 2
returned 1
returned 2
returned 3
data 1
data 2
data 3
But I want my response to be something like this:
Response timed out 1
Response timed out 2
returned 1
data 1
returned 3
data 3
returned 2
data 2
My solution was to use an array to keep track of pending promises. In this way, I can open a request, and race it against the previous, still pending requests that lost a race against a timeout promise. The races will end whenever one of the pending request promises is fulfilled. This will slowly build an array of requests every timeout period, until one of them is fulfilled. By slowly increasing the number of concurrent requests instead of discarding them after the timeout period, I want to increase the likelihood that one of the requests is fulfilled by my server with an erratic response time.
const timeoutLimit = 250;
const timeoutLimitMessage = 'Response timed out, retrying';
const requestLimit = 20;
const RequestLimitMessage = 'Too many requests';
/** Executes a request, retrying on timeouts, until it returns successfully. */
export default async function requestWithTimeout(request, promises = []) {
const timeout = new Promise((resolve, reject) => {
setTimeout(() => reject(timeoutLimitMessage), timeoutLimit);
});
const apiCall = new Promise((resolve, reject) => {
request
.catch((err) => reject(err))
.then((resp) => resolve(resp));
});
promises.push(apiCall);
const result = await Promise.race([...promises, timeout])
.catch((err) => {
if (err !== timeoutLimitMessage) {
return err;
}
console.log(timeoutLimitMessage);
if (promises.length > requestLimit) {
throw new Error(RequestLimitMessage);
}
return Promise.race([...promises, requestWithTimeout(request, promises)]);
})
.then((resp) => resp);
return result;
}