DEV Community

Cover image for Tips from open-source: Set a maximum time limit on fetch using Promise.race()
Ramu Narasinga
Ramu Narasinga

Posted on

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.

Learn the best practices used in open source

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"
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

I wanted to pick an example that is easy to understand. So, I searched in the wild.

search results on github

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);
  });
};
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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

https://stackoverflow.com/questions/46946380/fetch-api-request-timeout

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)
})
Enter fullscreen mode Exit fullscreen mode

— 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.

Top comments (0)