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, ...).