JavaScript Event Loop And Call Stack Explained
-
My goal with this article is to teach you how JavaScript works in the browser. Even though I’ve been working with JavaScript my whole career, I didn’t get how these things work until recently.
How JavaScript works in the browser
Before I dive into the explanation of each topic, I want you to look at this high-level overview that I created, which is an abstraction of how JavaScript interacts with the browser.
Don’t worry if you don’t know what all of the terms mean. I will cover each of them in this section.
Call stack
You’ve probably already heard that JavaScript is single-threaded. But what does this mean?
JavaScript can do one single thing at a time because it has only one call stack.
The call stack is a mechanism that helps the JavaScript interpreter to keep track of the functions that a script calls.
Every time a script or function calls a function, it’s added to the top of the call stack. Every time the function exits, the interpreter removes it from the call stack.
A function either exits through a return statement or by reaching the end of the scope.
Heap
The JavaScript heap is where objects are stored when we define functions or variables.Since it doesn’t affect the call stack and the event loop, it would be out of the scope of this article to explain how JavaScript’s memory allocation works.
Web APIs
Above, I said that JavaScript could only do one thing at a time.
While this is true for the JavaScript language itself, you can still do things concurrently in the browser. As the title already suggests, this is possible through the APIs that browsers provide.
Let’s take a look at how we make an API request, for instance. If we executed the code within the JavaScript interpreter, we wouldn’t be able to do anything else until we get a response from the server.
It would pretty much make web applications unusable.
As a solution to this, web browsers give us APIs that we can call in our JavaScript code. The execution, however, is handled by the platform itself, which is why it won’t block the call stack.
Another advantage of web APIs is that they are written in lower-level code (like C), which allows them to do things that simply aren’t possible in plain JavaScript.
They enable you to make AJAX requests or manipulate the DOM, but also a range of other things, like geo-tracking, accessing local storage, service workers, and more.
Callback queue
With the features of web APIs, we're now able to do things concurrently outside of the JavaScript interpreter. But what happens if we want our JavaScript code to react to the result of a Web API, like an AJAX request, for instance?That’s where callbacks come into play. Through them, web APIs allow us to run code after the execution of the API call has finished.
Let's have a look at an example:
const a = () => console.log('a'); const b = () => setTimeout(() => console.log('b'), 100); const c = () => console.log('c'); a(); b(); c();
You can probably already think of what the output will look like.
setTimeout is being executed concurrently while the JS interpreter continues to execute the next statements.
When the timeout has passed, and the call stack is empty again, the callback function that has been passed to setTimeout will be executed.
The final output will look like this:
a c b
But what about the callback queue?
Now, aftersetTimeout
finishes its execution, it doesn’t immediately call the callback function. But why’s that?Remember that JavaScript can only do one thing at a time?
The callback we passed as an argument to
setTimeout
is written in JavaScript. Thus, the JavaScript interpreter needs to run the code, which means that it needs to use the call stack, which again means that we have to wait until the call stack is empty in order to execute the callback.You can observe this behavior in the following animation, visualizing the execution of the code we saw above.
Calling
setTimeout
triggers the execution of the web API, which adds the callback to the callback queue. The event loop then takes the callback from the queue and adds it to the stack as soon as it’s empty.Unlike the call stack, the callback queue follows the FIFO order (First In, First Out), meaning that the calls are processed in the same order they’ve been added to the queue.
Event loop
The JavaScript event loop takes the first call in the callback queue and adds it to the call stack as soon as it’s empty.
JavaScript code is being run in a run-to-completion manner, meaning that if the call stack is currently executing some code, the event loop is blocked and won’t add any calls from the queue until the stack is empty again.
That’s why it’s important not to block the call stack by running computation-intensive tasks.
If you execute too much code or clog up your callback queue, your website will become unresponsive because it cannot to execute any new JavaScript code.
Event handlers, like onscroll, add more tasks to the callback queue when triggered. That’s why you should debounce these callbacks, meaning they will only be executed every x ms.
Ref: https://felixgerschau.com/javascript-event-loop-call-stack/