Asynchronicity in JavaScript

Introduction

Asynchronicity is one of the most used and most important aspect of “JavaScript”, so let’s review it. In this post, we’ll start by learning what “v8” and the “events loop” are. Then we’ll talk a bit about callback functions before exploring what promises and observables are. The idea of this post is not to deep dive in all of these concepts but rather get a clear overview of them to see what to use, when to use it and how it works.

v8 and the events loop

Besides being a vegetable juice, “v8” is also the name given to the engine developed by “Google” to run “JavaScript”… synchronously…

Wait what? I’m reading a post about asynchronicity and you tell me that JavaScript is actually synchronous?!

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.

So even though you probably used these features, it’s worth knowing that they are actually provided by the browser and not the JavaScript engine itself.

Ok, I get what “v8” is, but what is the events loop?

Before talking about that, we need to know a bit how the execution of a “JavaScript” code works. Consider the following code:

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.

You actually know that because you have a brain. Indeed, if you had no brain, therefore no memory, you’ll be unable to remember that “bar” has been called by “foo”, so you wouldn’t know that the execution must resume at line 4 (to be fair, you’ll also have other kind of issues :-D). That’s why the engine comes with its own memory called the “stack”. It also comes with a “heap” but we won’t talk about it here for the sake of simplicity. Basically, the stack helps “JavaScript” keeping track of the current script execution.

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:

foo()
main():11

So now, “JavaScript” knows that it has to execute “foo”, but it also knows that once “foo” is executed, the execution must continue into “main” at line 11 (of course, the stack does not contain this line number but once again… Simplicity…).

Then, the call to “bar” is met:

bar()
foo():3
main():11

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:

Untitled Diagram.png

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:

Untitled Diagram (1).png

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:

Untitled.png

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.

Callback functions

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

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:

Waw…

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/await

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.

As the error states, you can only use “await” in an “async” function, so if you try to use it in the main function of “JavaScript”, you’ll get this error as the main function is not flagged as “async”. Most of the time, you won’t get this issue because your code will be called by a DOM event (or a “JavaScript” framework) and in that case, you’ll be able to flag it as “async”.

For instance:

Observables

Observables are another concept used to execute code asynchronously in “JavaScript” and as, like every developer on Earth, I don’t like to repeat myself, I’d advise you to read my blog post on that topic.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.