Code:
setTimeout(() => console.log("1"), 1000)
const startTime = Date.now()
while (Date.now() - startTime < 5000);
setTimeout(() => console.log("2"), 0)
setTimeout(() => console.log("3"), 0)
The output of this sample is:
2
3
1
Why it works this way ?
In this example the loop blocks the thread for 5 seconds or so. At this moment first setTimeout should have been finished and go out of the macrotask queue first, but it doesn't.
This appears to be a side effect of setTimeout
optimisation in Chromium, but I can't find documentation that specifically would make this a bug. Quite the opposite, setTimeout
does not guarantee execution order.
If you change the timeouts to 1ms it works as you expect:
setTimeout(() => console.log("1"), 1000)
const startTime = Date.now()
while (Date.now() - startTime < 5000);
setTimeout(() => console.log("2"), 1)
setTimeout(() => console.log("3"), 1)
It looks like, in Chromium at least, setTimeout
has an optimisation on 0ms to skip checking for timed out actions, and instead just runs them on the next loop (like requestAnimationFrame
would) before checking for any other expired timeouts.
Arguably this is desirable behaviour when using a 0ms timeout, and I don't think it's a bug because there's no guarantees on the timeouts - that's in the spec.
I'd recommend using requestAnimationFrame
over setTimeout(..., 0)
for consistency.
Original answer....
Your problem is that this:
for (let i = 0; i < 10000; i++) {
for (let j = 0; j < 10000; j++) {}
}
Is not a slow operation. Most JS engines can optimise loops and additions like this.
You can use await
to stall your execution and come back:
/** Wait for the given millseconds */
function wait(ms) {
return new Promise(r => setTimeout(r, ms));
}
async function demo() {
setTimeout(() => console.log('1'), 1000)
await wait(5000);
setTimeout(() => console.log('2'), 0)
setTimeout(() => console.log('3'), 0)
}
demo();
But, that doesn't block the JS - you'll get 1
after 1000ms
and then wait 4 more seconds for 2
and 3
.
If you want to block the executing JS that's harder, as they've worked very hard to remove blocking actions. For instance fetch
doesn't support it, and while XMLHttpRequest
has an async=false
flag you could use to block while downloading something large, I think some browsers ignore that now.