JavaScript is a single-threaded programming language, this means we can do only one thing at a time. Still, we can make scripts run asynchronously. It is possible because of the runtime model based on the event loop in JavaScript. The event loop is responsible for executing code, maintaining a queue of events, and executing queued tasks. So by using some smart data structures, we are able to execute tasks asynchronously and make JavaScript multi-threaded.
Let’s first understand how JavaScript works under the hood. We’ll talk about some concepts that are being used by JavaScript engines to implement and optimize scripts.
Understanding Call Stack
The call stack is a mechanism for a JavaScript interpreter(browser) to keep track of functions that are being executed or waiting to be executed in a script. So when we invoke a function to execute, the function gets pushed to the call stack, and when the program returns from a function, it means the function is pooped out from the call stack. The stack follows the LIFO - Last In First Out principle.
When a script is executed, the JS engine creates a global execution context and adds it to the call stack.
Any function that will be executed later will come on top of the Global Execution Context
Now, we’ll execute a function
function sayHello() {
var name = "Random Citizen"
console.log("Hello There!", name)
}
sayHello()
When sayHello
is invoked, we push it into the call stack. Execute all the lines, declare variables in-memory heap, and push the console.log
function in the call stack. We execute, each function one by one and once all of them are executed and returned, we pop each function out of the call stack.
Event queue
The event queue is responsible for sending new functions to the call stack for processing. JS uses a queue data structure to maintain the way we execute functions. Let’s take an example, we’ll use a setTimeout
function which will log after 2000 milliseconds(2s).
(function() {
setTomeout(function() {
console.log("Executed after 2s")
}, 2000)
})()
When setTimeout() is called, the browser starts a timer, when it expires the callback function is put in the event queue. The event queue has to push this callback function into the call stack for execution. The event loop waits for the function stack to be empty.
Thus event loop works in a cyclic manner, it continuously checks on the call stack. If the call stack is empty, new functions are added to the event queue.