Friday, December 13, 2013

Making sense of Javascript task queues processing

It seems that even seasoned JavaScript developers having trouble understanding how task queues are organised in JavaScript and what the order of tasks execution is. It seems pretty  straightforward till you run into situation where it just doesn't make sense. In this post I will try to clarify important details of how task queues are organised. Lets start from beginning  and build on examples as we go.

JavaScript is a single threaded language. It basically means that only single block of the code can be run at a time. Program simply runs line by line, through loops, conditionals etc to its end. Sometimes its quick and sometimes it takes long. If so then browser indicates its busy. This in turn often results in "Unresponsive script" sort of popup as below:






Consider this code:

HTML:

<div id="box">nothing here yet</div>

JS:

var box=document.getElementById("box");

for(var i=0;i<10000000;i++) {
     box.innerHTML=i;   
}

It starts a fairly long loop and inside of the loop div is updated with current value of i. As you will see when the program runs, browser will not respond and will look busy. The div that is being referenced inside of a loop will most likely not show updated value till the loop ends and program exits.

Now, two very important points:

1. If the browser will update the UI and when this is gonna happen depends on browser implementation. Most likely it will happen when the program will run to the end but its totally up to the browser.

2. Browser will not respond to asynchronous events e.g. mouse clicks as long as the single thread of JS code runs. Click all you will, nothing will happen till the very end. Program will not jump to any handler and will keep running till its finished. No magic here, one thread only.

To confirm just that try the code below. This time we added mouse click listener that opens alert when document is clicked.

HTML:

<div id="box">nothing here yet</div>

JS:

var box=document.getElementById("box");

document.onclick=function (e) {
    alert("DOH!");    
};

for(var i=0;i<10000000;i++) {
     box.innerHTML=i;   
}

Now, while program runs click somewhere in in the document. None of the browsers respond when the script is busy. So what happens to asynchronous events when single JS thread runs? Are these completely lost? Not to worry.. these are put in a task queue so their callbacks could be executed when currently running JS program reaches its end.

Now imagine that instead of click event some other event occurs e.g. window load event, timeout is fired etc. Each of these would be placed in a task queue.

Ok, this is straightforward stuff so lets take it a bit further and have a look at code below:

HTML:

<div id="box">nothing here yet</div>

JS:

function homerSays() {
    alert("hmm.. Doghnuts...");
}

setTimeout(homerSays, 5000);

document.onclick = function () {
    alert("DOH!");
}

for (i = 0; i < 1000000; i++) {
    box.innerHTML=i;
}

Again it seems straightforward, the result however, depending on a platform you use, will surprise many. It simply schedules function call in around 5 seconds (setTimeout(..)), then attaches listener to click on document and runs fairly long loop.

Now it would seem that when you click the document before 5 seconds has passed it should schedule click event and place it in a task queue. Then after 5 seconds is passed timeout event should fire and should be placed in a queue again, somewhere after earlier click event. One might think its certain that when the program is finished the outcome will be alert with "DOH!" (from click callback) followed by alert with "hmm.. Doghnuts..." (timeout). Once again however this depends on browser implementation. Run the code above in various browsers and see for yourself. E.g. I consistently get different results in Chrome and Firefox.

To see why this is happening lets go line by line and explain details.

When program runs it gets to the line with:

setTimeout(homerSays, 5000);

So what happens now? At this point its extremely important to understand few points:

1. The line schedules event to be fired in 5 seconds from this very point. This is important not to get confused and understand that this will be fired (as close as possible to) 5 seconds from now, NOT from when the execution of JS will finish. Pretty basic stuff yet many gets confused.

2. At this point there is nothing placed into task queue just yet. Event will be placed into a task queue at the time when its fired, so in around 5 seconds from now. Program might be still running by then and event will have to wait in a queue for its execution.

3. Program registers onclick handler and enters a loop.

4. The loop is running now and setTimeout has not fired yet as 5 seconds has not passed. User clicks mouse button. As program is busy and this places click event in a task queue.

5. 5 seconds has passed and timeout fires. This places timeout event in a task queue as well.

6. Loop finishes and alerts pop up on the screen.

Now, lets take Chrome as an example. Surprisingly timeout handler will be executed first, even though that mouse had been clicked before timeout fired.

Why is this happening then? It's happening because browser might maintains more than one task queue. Its important to know that how many task queues for event types are maintained it depends on browser implementation. According to specs it might be one single queue for all event types, or multiple queues, each for specific event type. 

Specification is pretty clear on it:

For example, a user agent could have one task queue for mouse and key events (the user interaction task source), and another for everything else.

So depending on platform events will be placed all in one or in more specific queues. So what is the big deal you ask? No matter how many queues, these events should be processed in order, right? If click happened before timeout fired it still should be executed before the timeout task? The answer is it depends on browser implementation. Once again specification clarifies that:

The user agent could then give keyboard and mouse events preference over other tasks three quarters of the time, keeping the interface responsive but not starving other task queues, and never processing events from any one task source out of order.

The example above clearly gives different results in different browsers. To sum it up if there are events in different task queues its totally up to a browser to decide which queue should be processed first. Without knowing this its nearly impossible to understand browser behavior at times.

Ok, to sum all the above up in few short points:

1. Only on thread of JavaScript can run at a time. If JavaScript runs and asynchronous even occurs (click, timeout fires etc.) this event is put in a queue.

2. Browser might have one or more task queues. User interaction events e.g. mouse clicks, keypress etc. might go in one queue, timeouts in other and so on. This depends on browser implementation.

3. When JavaScript program runs to its end browser decides which task queue needs to be processed first. This again depends on browser implementation and its easy to get confused when things seem out of order.


No comments:

Post a Comment