16 May 2021
Implementing sleep in JavaScript with Promises
Promises are a super useful way to manage execution of asynchronous code in JavaScript. You can find helpful libraries in npm that cover almost any need for generic Promise utilities, but
- it’s good to internalize how you can write your own Promises
- they’re often super short and trivial to write
- you can easily avoid extra dependencies
For example, if we wanted to make a promisified version of setTimeout it’s as simple as this:
const sleep = (delay, resolveValue) => new Promise((resolve) => {
setTimeout(() => resolve(resolveValue), delay);
});
or in TypeScript:
const sleep = <T extends any>(
delay: number,
resolveValue?: T
): Promise<T> =>
new Promise((resolve) => {
setTimeout(() => resolve(resolveValue), delay);
});
With that you can very simply add a timeout delay into any async function:
await sleep(1000); // sleep for 1 second
I chose to allow for passing through an optional resolveValue
. This can be useful if you’re chaining Promises and want to pass a previous response to the next .then()
, but it is relatively useless if you’re using async/await
.
Here’s an example of using it with Promise.all()
to make the code wait for a minimum duration before executing code after an API call:
const [resp] = await Promise.all([
fetch('/api/users/me').then(resp => resp.json()),
sleep(3000)
]);
A neat thing about this setup, is that if the fetch
errors, the rejection will be raised before the sleep
completes. Otherwise the API call will execute as fast as it can, but we’ll always have to wait a minimum of 3 seconds before proceeding.
While a user interface should generally be as snappy fast as possible for the best user experience, sometimes—if it’s too fast—it will throw off the user. This makes sense when the user is doing a rare and monumental action, like clicking a button to convert from a consumer to business account or start a trial. Using the above setup with some thoughtful loading messaging (“finalizing your account”, “starting the reactor”, etc.) and then a nice payoff (confetti, celebrate fireworks) can create a tighter user experience than immediately updating things as soon as an API call returns!