How to sleep in JavaScript

JavaScript has no blocking sleep, and that is deliberate: one thread runs your code and the timers and the clicks, so a thread that sleeps stops all three.

The one to use

const sleep = d => new Promise(r => setTimeout(r, d));
await sleep(300);
await sleep(300) took 301 ms

setTimeout wrapped in a Promise and awaited. Three tokens, no dependency.

What it actually pauses

after starting slow(), we are already here at 0 ms
slow done at 200 ms

Only the async function it appears in. await yields to the event loop, so everything else keeps running — timers fire, the page stays responsive, other awaited work proceeds. This is the difference from sleep() in a language with threads, and it is a feature.

Without the await, setTimeout pauses nothing at all:

the line after setTimeout runs at 0 ms
timer fired at 100 ms

What a busy-wait costs

function busySleep(d) { const end = Date.now() + d; while (Date.now() < end) {} }
busySleep(300) took 300 ms
did the 50 ms timer fire during it? false
after yielding, has it fired now? true

The loop does block, which is why people reach for it. The price is in the second line: a timer due at 50 ms did not run until the loop finished, 250 ms late. Nothing else could run either — in a browser that is a frozen page, no scrolling, no clicks, and after a few seconds a “page is not responding” dialogue. It also burns a core the whole time.

The delay is a minimum

sleep(0) actually took 1 ms
sleep(5) actually took 5 ms
10 chained setTimeout(...,0) took 12 ms, not 0

setTimeout schedules; it does not promise. The callback runs no earlier than the delay, once the current work has finished. A delay of 0 is clamped to 1 ms in Node; browsers additionally clamp to 4 ms once timers nest more than five deep.

Sequential or parallel is your choice

for..of with await : 301 ms (sequential)
map + Promise.all  : 100 ms (parallel)
forEach with async : 1 ms <- did not wait at all

Three 100 ms waits, three results. for..of with await waits each time. Promise.all starts all three and waits once.

The third line is the bug worth knowing. forEach calls your async callback, gets a promise back and throws it away — it has no idea what to do with one. So the loop finishes immediately and the code after it runs while the work is still pending. It looks exactly like the working version.

The ones you might not know

timers/promises setTimeout(200) took 200 ms

Node ships a promise-based timer in node:timers/promises, so on the server you need not write the wrapper.

Atomics.wait(..., 200) blocked for 200 ms

Atomics.wait really does block the thread, and unlike the busy loop it does not consume CPU while doing so. It is not allowed on a browser’s main thread — which is the right restriction, and tells you where the remaining legitimate use is: inside a worker.

About Netcup (advertisement)

The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).