3

I don't quite understand how timers work in Javascript. JS is single threaded so code runs and e.g. user clicks the button the click is added to the queue. The queue was empty so the queue looks like that:

[click]

That will be executed as soon as our code currently running finishes is that right? Lets say the same code still running though and is not finished just yet and its schedules setTimeout(fn,3000).

Now I'm not sure if I got this right. This fn is not added to the queue but will fire at some point close to 3000ms from this very moment (NOT from when this code execution ends). If at this time when that event wants to fire (in around 3000ms from now) other code is executing (maybe [click] from above) this fn will be put at the end of the queue then.

Now back to our original running code, [click] is in a queue and code runs further. Now our running code changes style property of some element in DOM and adds the border. This change will have to be done when browser will refresh UI. It will not be visible immediately as JS is running some other code and so is added after the click so the queue looks like that now:

[click] [UI refresh - it will change border amongst other things possibly]

So now queue contains two events to be called when currently run JS ends. It will call [click] and user still will not see the change in the border our earlier code requested. When click is finished next event from the queue will jump in which is UI refresh. It will do whole bunch of drawing probably, including our border change we requested when earlier code run.

If during the time when click event is being executed or when UI makes changes to the display and is redrawing the screen the timer we scheduled fires, fn will be added to the queue and executed as soon as possible.

Is my understanding correct? If someone could clarify if I misunderstood something and explain where I got it wrong that would be great. Once I clarify this I will extend this question to setTimeout(fn,0) trick as this what really gets me confused even further.

spirytus
  • 10,726
  • 14
  • 61
  • 75
  • 1
    Have you seen John Resig's "[*How JavaScript Timers Work*](http://ejohn.org/blog/how-javascript-timers-work/)?" The exact details can be implementation-specific. But, in general, timers work by adding the function to the event loop (the queue) when their timeout expires (a timeout of `0` just adds it immediately to the end of the event loop). After that, when that function becomes the "*next in line*," if it isn't immediately, the event loop will call/execute it. – Jonathan Lonowski Sep 16 '13 at 23:57
  • yes I've read it as well as Secrets of JS ninja and that's how I understand it. I'm not sure however how the redrawing UI is scheduled, is it something that waits in a queue as other events and browser adds it to the queue when it feels like? So lets say I have queue [click][some function][UI refresh (added by browser? when? why here?][some other callback]. Then [click] will be run, then [some function] then browser will redraw window and then run [some function]? Sorry if its confusing, having trouble explaining it clearly even to myself – spirytus Sep 17 '13 at 00:03
  • So if in a queue I have [click][fn][fn2] and after [click] is executed browser might decide to put UI redraw in front of [fn], or after [fn] and in front of [fn2]? So basically it can push in UI redraw at anytime somewhere in a queue? – spirytus Sep 17 '13 at 00:24
  • I think redrawing is simply the default action the browser takes when it returns to the event loop and the queue is empty. – Barmar Sep 17 '13 at 00:37
  • I posted sort of related question at http://stackoverflow.com/questions/18839413/idont-understand-how-settimeoutfn-0-in-javascript-works/18839461?noredirect=1#18839461 where I'm having problem with setTimeout(fn,0) – spirytus Sep 17 '13 at 00:40
  • @jonathanLonowski seems like one of your really interesting comments with a link to jsfiddle dissappeared. Did you delete for any reason or SO lost it somehow (or SO police clowns deleted it?) – spirytus Sep 17 '13 at 01:29
  • @spirytus Sorry. http://jsfiddle.net/coiscir/rvQZJ/. Removed it to review further. But, it does seem my earlier comment wasn't right about precedence being given to redraws. It seems Chrome might do so at times, but not always. And Firefox seems to consider them equal. – Jonathan Lonowski Sep 17 '13 at 02:07
  • @JonathanLonowski no worries at all, I think your example is very good as it shows that after running first timeout browser will redraw. It makes sense as when the first timeout with "one" is run and before the "two" fires, code in "one" requests redraw and as JS is busy running, redraw request is placed in a queue. Then "two" fires and as JS is still busy running timeout "one" code it is added to the queue right after the UI redraw that is already there from "one". Do I make sense here? It helps me to understand it more cleare but please let me know if this is not how it works. – spirytus Sep 17 '13 at 02:43
  • @JonathanLonowski Also this seems pretty consistent on my system and Chrome/FF behaves same. BTW I really appreciate time you spend on this question, it really helped me and if you could make it somehow into an answer I will be happy to accept it – spirytus Sep 17 '13 at 02:44
  • In general you seem to have understood the event queue. However, an UI redraw is not an event (that would need to be scheduled)! It can occur whenever the browser feels to, being completely independent of javascript execution - most likely even running in an other thread. [Only some DOM actions *require* a reflow](http://stackoverflow.com/questions/510213/when-does-reflow-happen-in-a-dom-environment), but those happen synchronously then and do not necessarily trigger a repaint. – Bergi Sep 17 '13 at 04:24
  • @Bergi so its up to browser when the redrawing, so changes to what is visible on the screen happen? Can it happen at any time really including when some part of JS code is run? In previous example discussed above it seems to happen when some code is finished ("one"), then redrawing happen, and then something else is pulled out from event queue ("two") but is it really completely independent of JS execution and is not really placed in event queue? Just confirming because had no idea and thought all goes in a queue and is pulled out from there – spirytus Sep 17 '13 at 08:43
  • Yes, it is completely browser implementation dependent. Of course it makes sense to do a reflow&redraw after an event was processed (or maybe when the event queue is empty again), but in a multithreaded browser it can also happen that the screen is drawn in parallel to the js execution. However, you need to see redrawing as a part of the main program loop, other parts are "doing UI", networking, or processing the next event loop task. – Bergi Sep 17 '13 at 14:53

1 Answers1

2

You are correct about the way the event loop works. It is a single-threaded environment where you have a bunch of tasks (or functions) which are scheduled for execution and the loop which executes them.

The UI refresh action is not outside of this loop as someone suggested. However it also isn't scheduled as separate task. It is executed synchronously, as part of the click handler.

[click [UI action] ]

synchronous/asynchronous

A synchronous action is one that blocks the code execution until it is done. In other words if you say to the JavaScript engine ("draw a border"), it will ensure that the border is drawn before the next line of your code is executed. You can easily verify this with the following example:

 $(".suggest-edit-post").css('background-color', 'red');
 $(".suggest-edit-post").css('background-color');// 'red'

So DOM manipulation is one of the few synchronous actions in JavaScript. Other such actions are alert, confirm, etc methods for opening a popup.

You can generally guess that some function is asynchronous by observing if it has a callback parameter (or some other mechanism for passing a callback function).

For example the ajax() function is asynchronous, so calling it on click looks like this.

[click[ajax.get]]           [ajax.success]
|       |       |       |       |       |       |       |       |     
1               2               3               4               5

So you do a get, there is a pause during which other stuff can be done and then the response arrives and your callback is executed. Then it can make other requests, which have their own callbacks etc.

setTimeout(fn,0)

All of this (the event loop) is handled by the runtime. This has some good and bad sides: It is good because you don't need to deal with threads processes, etc. - you just need to specify what you want to happen after, say, you receive a response for your ajax call and the runtime just makes it happen.

It is also bad, because you cannot really pause and schedule which action happens after which: once a task starts executing, it must be executed till the end before the next one starts.

If we don't have many things that happen simultaneously, this is a non-issue:

[click[ajax.get]]               [ajax.success]
|       |       |       |       |       |       |       |       |     
1               2               3               4               5

In this case the success function is executed at the moment that the response arrives.

But what happens if we scheduled some very complex data manipulation or animation to be executed in the pause:

[click[ajax.get]] [complex data manipulation/animation] [ajax.success]
|       |       |       |       |       |       |       |       |     
1               2               3               4               5

We might not want the execution of our success function (which may be showing some vital information to the user) to be postponed in favour of some other function, but we cannot rearrange the tasks, nor prioritize them, so there is no solution for this problem.

So sometimes we can be late. OK, but how late? Well, the engine tries to execute the stuff on time, so the amount of time that we may have to wait for a task is equal to duration of our biggest task. So if all our tasks are short we will be OK.

setTimeout(fn,0) allows you to split a task into several shorter tasks that are better processed in the loop.

[click[ajax.get]] [animation] [ajax.success] [animation] [animation] 
|       |       |       |       |       |       |       |       |     
1               2               3               4               5

Lets say that you have to update a bunch of HTML element with new data:

new_values.forEach(function(val, index){
  elements[index].text(val)
})

To do this in separate task, you just go:

new_values.forEach(function(val, index){
  setTimeout(function(){
      elements[index].text(val)
  }, 0)
})

Now instead of one big task, you have a bunch of smaller ones.

P.S. That is the only legitimate use of setTimeout(fn,0).

Jencel
  • 722
  • 7
  • 17