Sleep/delay in async code
A one-line sleep(ms) you can await inside an async function, wrapping setTimeout in a Promise so nothing else on the page blocks.
Last updated
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
// inside an async function:
// await sleep(500);
// runs after roughly 500msHow it works
JavaScript has no built-in blocking sleep, and that's deliberate — a real blocking sleep would freeze the single thread the page or server runs on, including its UI and every other pending request. Wrapping setTimeout in a Promise gives you the same-looking `await sleep(500)` call site without ever blocking anything: the event loop stays free to handle other work while the timer counts down.
resolve is passed directly as setTimeout's callback rather than wrapped in an arrow function, since setTimeout calls it with no arguments the Promise executor cares about — one less closure for the same effect.
Edge cases to know
- →Only the async function that awaits sleep() pauses — everything else (other requests, other event handlers, timers) keeps running exactly as if sleep() were never called.
- →The delay is a minimum, not a guarantee: if the event loop is busy when the timer fires, the resolution — and whatever runs after the await — is pushed back accordingly.
- →There's no way to cancel a pending sleep with this version; if you need that, pair it with an AbortSignal and reject when the signal fires.
Part by part
- new Promise((resolve) => setTimeout(resolve, ms))
- Wraps a callback API in a promise. resolve is passed directly as the timer's callback, so it fires with no arguments once the delay elapses.
- await sleep(500)
- Suspends the async function without blocking the thread. Everything else — rendering, other handlers, timers — continues running during the wait.
Common mistakes
function sleep(ms) { const end = Date.now() + ms; while (Date.now() < end) {} }A busy-wait blocks the only thread there is. In a browser the page freezes completely; in Node nothing else is served for the duration. It also pins a CPU core at full load for no work.
function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }items.forEach(async (item) => { await sleep(100); process(item); })forEach does not await its callback, so every iteration starts immediately and the delays run in parallel. Use a for...of loop when the sequencing is the point.
sleep(500); doSomething();Without await the promise is created and ignored, so doSomething runs immediately. The code reads as if it pauses and does not, which is a particularly quiet bug.
Questions
Is the delay exact?
No, it is a minimum. The timer fires only when the event loop is free, so a busy thread delays it further, and browsers clamp timers in background tabs to once a second or slower.
How do I make it cancellable?
Accept an AbortSignal and reject on its abort event, clearing the timeout as you go. Node also offers timers/promises setTimeout, which takes a signal directly and needs no wrapper at all.
Does this block the server in Node?
No — the function returns immediately and the runtime continues serving other requests. Only the awaiting async function is suspended, which is the entire difference from a busy-wait.
Should I use it for retries?
For simple backoff, yes, and multiply the delay each attempt rather than using a fixed one. Add a little randomness too, so many clients retrying after an outage do not arrive in step.