tricky setTimeout sequence

1k views Asked by At

Set of setTimeout calls with intervals 0,1,2,3.

function f() {

    setTimeout(function a() {console.log(10);}, 3);

    setTimeout(function b() {console.log(20);}, 2);

    setTimeout(function c() {console.log(30);}, 1);

    setTimeout(function d() {console.log(40);}, 0);

}

f();

Output: (from chrome. Hope would be the same in other browsers)

30

40

20

10

Can someone explain clearly why is the ordering not 30, 40, 10, 20? It is said browsers maintain a minimum 10ms or (spec says) 4ms interval. If so, validate the output with time metrics or whichever is convenient to explain this behavior. What minute detail am I missing to understand this awesome feature of the language?

Edited:

I know these functions are asynchronous. And I have read John Resig' blog couple of times. And I know a setTimeout' callback is not guaranteed to execute at the interval specified.

To be more precise, I expect an explanation that can explain the behavior in terms of execution queue, event loop, call stacks and timers.

2

There are 2 answers

0
Himanshu Tanwar On

In order to understand how the timers work internally there’s one important concept that needs to be explored: timer delay is not guaranteed. Since all JavaScript in a browser executes on a single thread asynchronous events (such as mouse clicks and timers) are only run when there’s been an opening in the execution.

Refer this and this for more details

0
JaffaTheCake On

As @Jebin pointed out in a comment to the OP, you're running into a race condition here.

Here's the spec: https://html.spec.whatwg.org/multipage/webappapis.html#dom-windowtimers-settimeout

Step 13 is where we wait, and that waiting can happen while script is currently executing. See http://jsbin.com/faguli/edit?js,console - in this example "10" is logged first because the script takes a long time to get to the next setTimeout.

At a guess, in Chrome, the task for 3rd setTimeout is queued before JS manages the 4th call to setTimeout.

So, in this case both Chrome and Firefox are spec-compliant, despite giving different answers.

One way to make this deterministic would be to process setTimeout calls as part of a microtask, and start the timers from there, but setTimeout is an olllllld API, so changes here may break the web.