The event loop and your code

The event loop is a core concept in Javascript. It is fundamental in order to understand how asynchronous code works – and how not to work with synchronous code.

Let’s start with a simple example. You have some code that is dependent on some variable to exist in memory. As long as it is not there, you’d like the code to retry again.

function retry() {
try {
console.log(window.toBe.orNotToBe);
} catch(e) {
retry();
}
}
retry();
// simulate a fetch action from the server
setTimeout(() => {
window.toBe = {orNotToBe: "That is the question!"}
}, 1000);
view raw gistfile1.txt hosted with ❤ by GitHub

The result of this code is the following error:

Figure 1: Our app ran out of space for one sychronous process – the call stack

One can simply see this error, find out that the solution would be to use some asynchronous mechanism and come out happy. If you just want the solution, skip the next part.

If you’d like to learn how this error happens, what is the call stack and what are synchronous and asynchronous events in the browser – read on.

Stackoverflow?

The famous website – stackoverflow – has a funny logo:

Image result for stackoverflow
Figure 2: Stackoverflow logo

Actually – the term stack overflow is exactly what happened to our little code. Our function called itself multiple time and filled the call stack – hence we exceeded the maximum call stack size. We overflowed it with callback calls.

What is this call stack?

The call stack is, well… a stack. A stack is a data structure. It’s a kind of array that allows us two basic operations: add to the stack and pop the last added value.

When we call a function it is added to the call stack. When this function calls a function, it is also added to the call stack (let’s call that called function a child).

When a function finishes running, it is removed from the stack. Remember that in a stack, we remove only the last added value. If the function has children, it needs to wait until all of them finish.

Here’s an illustration:

So what’s stackoverflow?

Let’s take what we learned about the call stack. If our function calls itself over and over again, the call stack will never clear. It will just add more and more calls to the same function:

Figure 3: Animated illustration of the call stack in action. Starts with the main function and moves on according to the order of calls.

So stack overflow would be the case of a function that calls itself indefinitely:

Figure 4: Calling the main function recursively without any stop condition

How does it look in the browser?

Going back to our retry function, and knowing what we know now about the callstack, we can now understand what’s the Maximum call stack size exceeded error mean.

The call stack can be seen in the browser and not just in fancy gif illustrtaions.

I run the above code in an HTML page and record using the performance tab. Here are the results:

Figure 5: Performance measurement – the retry function was called 688 times before the stack overflowed

In Figure 5 the retry function is called 688 times (the pink lines). After 688 times, we ran out of call stack space and the app crashed.

Not only our code doesn’t do what we want – it crashes. In order to be able to do what we want – retry periodically until something external happens – we’ll need to make our function asynchronous.

The Solution

Making our function asynchronous is quite easy. Using setTimeout can do just that:

function retry() {
try {
console.log(window.toBe.orNotToBe);
} catch(e) {
setTimeout(retry, 250);
}
}
retry();
// simulate a fetch action from the server
setTimeout(() => {
window.toBe = {orNotToBe: "That is the question!"}
}, 1000);

When running this code, after around 1 second, we get That is the question! logged in the console.

Let’s look at the recording result of this code:

Figure 6: Calls to the retry function from the time the process started. The Function Call parts are calls to retry from inside setTimeout.

The results in Figure 6 show that it took around 1 second to run the final call – which eventually logged the wanted result. In between, we had asynchronous calls to the function in roughly 250ms delay between each call.

We now have our retry mechanism. Problem solved!

Which came first, the chicken or the egg?

We learned about the Call Stack. With that knowledge, we know how synchronous processes work in Javascript.

What about asynchronous processes, like the one created by setTimeout? Or promises and ajax calls?

Here we go out from the zone of the Call Stack to an area called the Event Loop.

When we set our timeout, what we actually did was ask the system to take the callback given and add it to a callback queue once the timer runs out.

Figure 7: 1) The process starts with a user click. 2) It then adds the click function to the call stack. 3) click calls asyncCall and syncCall. 4) asyncCall sets the timeout and finishes 5)syncCall sets x to 5 and finishes. 6) Click finishes and removed from the stack 7) around 100ms later, logger is set in the callback queue by setTimeout.

Because our click function finishes, it is removed and the event loop now has an empty stack to send logger to once it is set in the callback queue.

If we had more asynchronous events set in between they would be in the callback queue as well – and our logger might just had to wait a bit longer until they finish and the call stack would be ready for it.

This is, in a nutshell, how asynchronous process come to be.

Not all asynchronous processes were born equal

There are types of asynchronous calls. For instance – there’s the setTimeout call, promises. I/O (e.g. user interaction) etc. Each has a different priority to enter the Event Loop.

For instance, if we have a callback from a setTimeout and a callback from a resolve Promise – the Promise‘s callback wins and gets to the call stack first.

I won’t go into more details than that in this article.

Summary

Let’s recap our journey so far:

  • We wanted to create a retry mechanism to some variable on the global scope (window).
  • We saw that naively trying to call a retry function will results in stackoverflow. Hence, our solution was to make the consecutive calls to retry asynchronous.
  • We did that using setTimeout and it solved our issue.
  • We then explained how asynchronicity is achieved in Javascript – via the event loop and callback queue.

I hope the mechanism is a bit clearer now. As usual, leave comments below or message me directly if you’d like to debate over this (or anything else 🙂 ).

Thanks a lot to Yonatan Doron from Hodash.dev for a thorough review!

Sign up to my newsletter to enjoy more content:

0 0 votes
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments