For more tips like this, sign up to the weekly newsletter!

Use Promise.race for user-friendly timeouts

Imagine having a long-running process that usually runs within a second, but sometimes it takes longer. A waiting spinner is a user-friendly way of handling this, but if the process is fast it's better not to show it.

How to make sure that only for the long-running processes will the spinner be shown?

Let's say you have a code like this:

const process = async () => {
  // get the results
  
  // show the results
}
Use Promise.race

Promise.race gets an array of Promises and resolves (or rejects) with the first result. Along with the process() call, let's have a timeout Promise also. Only if the latter finishes first we show the spinner.

First, let's make sure that the processing one never rejects and the timeout never resolves:

[
  process().catch(() => undefined),
  wait(1000).then(() => Promise.reject())
]

The .catch(() => undefined) makes sure no matter what the result of the process() call is, the Promise will be resolved and never rejected. Similarly, the .then(() => Promise.reject()) converts a resolved Promise to a rejected one.

Then we need a Promise.race and a catch on the result. A call to the catch means the timeout is hit:

Promise.race([
    process().catch(() => undefined),
    wait(1000).then(() => Promise.reject())
  ])
  .catch(() => {
    // show the spinner
  })

This makes sure the spinner is only shown if the processing goes longer than a second.

Try it
References
Learn more: