Tips from open-source: Set a maximum time limit on fetch using Promise.race()
This article demonstrates how Promise.race can be used to set a maximum time limit on a fetch request. Originally, I found Promise.race() in the next.js source code.
Promise.race()
Promise.race requires an iterable of promises and returns a promise that settles first in the provided iterable promises.
// source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
const promise1 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'two');
});
Promise.race([promise1, promise2]).then((value) => {
console.log(value);
// Both resolve, but promise2 is faster
});
// Expected output: "two"
I noticed Next.js uses Promise.race in a worker related file and is quite advanced.
// source: https://github.com/vercel/next.js/blob/canary/packages/next/src/lib/worker.ts#L121C15-L129C16
for (;;) {
onActivity()
const result = await Promise.race([
(this._worker as any)[method](...args),
restartPromise,
])
if (result !== RESTARTED) return result
if (onRestart) onRestart(method, args, ++attempts)
}
I wanted to pick an example that is easy to understand. So, I searched in the wild.
That is when I found ChatGPT-Next-Web uses Promise.race([fetch(CN_URL), timeoutPromise(5000)]).
timeoutPromise
const timeoutPromise = (timeout) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error("Request timeout"));
}, timeout);
});
};
Promise.race([fetch(EN_URL), timeoutPromise(5000)])
Promise.race([fetch(EN_URL), timeoutPromise(5000)]) — What this means is that fetch should finish executing in 5 seconds otherwise, it is timed out with a promise rejected.
Upon closer inspection, I found EN_URL and CN_URL translate to http://raw.fgit.ml/f/awesome-chatgpt-prompts/main/prompts.csv and http://raw.fgit.ml/PlexPt/awesome-chatgpt-prompts-zh/main/prompts-zh.json respectively.
// source: https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/blob/3513c6801e0fd771ccb5784dcaefcac6d50245e4/scripts/fetch-prompts.mjs#L26
async function main() {
Promise.all([fetchCN(), fetchEN()])
.then(([cn, en]) => {
fs.writeFile(FILE, JSON.stringify({ cn, en }));
})
.catch((e) => {
console.error("[Fetch] failed to fetch prompts");
fs.writeFile(FILE, JSON.stringify({ cn: [], en: [] }));
})
.finally(() => {
console.log("[Fetch] saved to " + FILE);
});
}
main();
If the fetch is not resolved in 5 seconds, timeoutPromise rejects and the error is caught in catch block that writes to file with stringified {cn: [], en: [] } as shown above.
Other ways to abort a fetch request
I googled and found the following stackoverflow answer to be insightful
stackoverflow.com/questions/46946380/fetch-..
Using a promise race solution will leave the request hanging and still consume bandwidth in the background and lower the max allowed concurrent request being made while it’s still in process.
Instead use the AbortController to actually abort the request, Here is an example
const controller = new AbortController()
// 5 second timeout:
const timeoutId = setTimeout(() => controller.abort(), 5000)
fetch(url, { signal: controller.signal }).then(response => {
// completed request before timeout fired
// If you only wanted to timeout the request, not the response, add:
// clearTimeout(timeoutId)
})
— Picked from the above stackoverflow answer.
Conclusion
Usage of Promise.race depends on your usecase. I found that Next.js source code uses Promise.race in an infinite for loop with a breaking condition in a worker related file but I wanted to pick an example that is easy to understand. So, I searched in the wild.
And found a simple, easy to understand example, Promise.race can be used to set a maximum time limit on fetch request but this request fetches the prompts as csv and json in ChatGPT-Next-Web.
However, stackoverflow answer suggests that “Using a promise race solution will leave the request hanging and still consume bandwidth in the background and lower the max allowed concurrent request being made while it’s still in process.”
This makes me conclude that, use Promise.race only if you are fine with the fetch request hanging, otherwise use AbortController to abort the fetch request altogether.