v8 and the events loop
Well, not exactly. Actually, while “v8” is executing code synchronously, browsers add some functionalities allowing asynchronous code to be run:
- setTimeout/setInterval can execute some code after a specified amount of time.
- xmlHttpRequest can make an AJAX requests.
- DOM events can run code when a specific event occur.
Ok, I get what “v8” is, but what is the events loop?
When “foo” is called at line 11, it’s not difficult for the engine to know what to do. Indeed, it simply jumps to the “foo” function and executes it. The same applies for the call to “bar” in “foo”. However, once “bar” is executed, how does the engine know where to continue the execution? For us, it’s pretty obvious…
“bar” has been called by “foo”, so the execution just jumps at line 4 and continues.
Let’s see how the stack is used for the code above in a oversimplified way. Imagine that your script is identified as “main”, when it reaches the call to the “foo” function, the stack will look like this:
Then, the call to “bar” is met:
So now, “bar” is executing. Once the end of the function body is reached, “bar()” is removed from the stack and the engine picks up the value on the top of it to know what to do next, which is why the execution jumps back in “foo” at line 3 and continues. Once again, when “foo” is executed, it gets popped up from the stack and the execution can resume after the line “11” of “main()”.
This is the idea behind the execution mode of “v8”, where, as a reminder, asynchronicity is not feasible.
Okay, well… But you still didn’t tell me how I can execute code asynchronously
Now that you know how the stack works, let me introduce the “events loop”. This loop is actually not part of the v8 engine but is brought by the browser itself. The “events loop” is used by it to execute asynchronous tasks.
Let’s consider the following code:
As you probably know, the “setTimeout” function executes a function after the specified amount of milliseconds which is why the code above displays “foo started” and “foo finished” and only 2 seconds after, “bar executed” but how does that work.
Let’s represent the execution of this script at line “4” by this wonderful picture:
The “events loop” does not work alone, it uses a tasks queue. Basically, the idea is that every asynchronous code that needs to be executed are added to the tasks queue. Note that it is a queue, not a stack, so it’s not LIFO (last in, first out) but FIFO (first in, first out).
So when are they executed then?
When the stack is empty, and not before. So in our example, when the “setTimeout” is executed, the stack and the tasks queue remain the same. However, after 2 seconds, the task queues gets updated like this:
I still don’t know the role of the “events loop”!
As its name implies, the “events loop” is a loop that keeps on checking the state of the stack and the tasks queue. If the stack is not empty, nothing happens, if the tasks queue is empty, nothing happens, however, if the stack is empty and the tasks queue is not, the items in the tasks queue gets one by one added on the stack and executed. So the “events loop” is just a loop ensuring that all asynchronous code is executed as soon as possible.
That’s awesome! Nice, simple and issue free!
Well even though I agree with the awesome, nice and simple part, I have to disagree with the “issue free” one. Well, let’s be clear, there is no issue with the “event loops” but this architecture implies something important: do not trust setTimeout/setInterval for critical task. For instance, consider the following code:
The idea is to:
- Display the current date.
- Use “setTimeout” to display the current date one second later.
- Make a very long calculation and display it.
Let’s execute this code and see what the console has to say about it:
Even though we asked “setTimeout” to display the date one second after the display of the first date, we see that it gets displayed 10 seconds after! If you understood correctly the explanation of the “events loop”, this shouldn’t come as a surprise. Indeed, the issue is that the calculation takes 10 seconds to be done. Meaning that during this execution, the stack won’t be empty, as it is still executing “main()”, so the “events loop” won’t be able to pick the callback function from the tasks queue to display the date. It’s only when the “for” loop is done that the “events loop” is able to do it, so 10 seconds later. Therefore, you should not trust setTimeout/setInterval to execute critical tasks.
Aynchronicity wouldn’t be possible without callback functions. If you have a look at the code using “setTimeout” above, how would you define what code to execute without the callback function?
A callback function is a function (no kidding?!) that you can pass to another function for it to execute. In the case of “setTimeout”, it’s a function that gets executed when the specified amount of milliseconds is passed (with the limitation that we now know). Even though it’s pretty cool, this could quickly lead to complicated code.
We have three functions that simply multiply their first argument by itself a certain amount of time (** means ^). Forget about the stupidity of these functions and the fact that it could be merged in one function, just focus on the fact that we say that doing the calculation is actually an asynchronous task (it’s not, but let’s say it is).
As the task is asynchronous, we need a callback function (second parameter) to be executed once the calculation is done. So using these functions is pretty straightforward. For instance, if I want to have the square of “5”:
This would simply display “25” in the console and we’re good to go. However, let’s imagine that you want to do (((5 ^ 2) ^ 3) ^4). Then the code would become:
You’d get a valid result but the code becomes more and more ugly/complex, especially if, like here, you use the same variable name multiple times (ok, what “result” is this?!). This code turned out to be a “callback hell“.
Promises are a nice (but not the only) way to fix a callback hell. Basically, a promise allows to execute some code and be notified once it’s done. Executing a promise returns a “thenable” object. It’s called like this because you can call the “then” function on this object to specify the function to execute once the promise is done. Let’s update our code above to use promises.
Now, you can call these functions like this.
Awesome, isn’t it?
Hem… Well, how is this better?! There is more code and we still are in a callback hell o_O
Indeed, you’re right. However, “promises” have a nice characteristic: You can chain them by returning another promise in the “then” function, meaning that the code could be rewritten like this:
Way better, isn’t it? So now, we’re still having asynchronous code executed but the way to use it is way more clean, but wait, it gets better !
async and await are (ES6) keywords used to ease the usage of promises. Making a function asynchronous is as simple as adding “async” in front of the “function” keyword:
Now, you can use “await” to call these methods like they were synchronous. Keep in mind that this does not turn your asynchronous code in synchronous one, it will only look like it.
I tried it but Google Chrome complains that await is only valid in async function.