Reputation: 10946
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.
Upvotes: 3
Views: 960
Reputation: 794
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] ]
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.
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)
.
Upvotes: 2