Event Loop in JavaScript
Having a conversation with a friend of mine about Javascript engines, I asked him if he knew the order of which code statements are executed in a small JS script containing setTimeout functions and he seemed a little puzzled avoiding the question with a generic answer saying, “well, I think that the code might be executed in a top-down manner”, which is not the case. To answer this question properly we must understand how the event loop in Javascript works and despite the fact that this might sound much theoretical, knowing the order of execution in your code can save development time and improve performance.
A Single-Threaded Language
Javascript is single-threaded but most of developers might not even be aware of that because we don’t really need to understand how the interpreter works under the hood to build reponsive applications with it. Though, we might have picked a non-reponsive tab in a browser that caught an infinite loop, causing the freezing of the main thread, blocking any further code execution which indicates that the interpreter can only execute a single task at time. Of course, if we put any infinite loop in our code the thread will become totally idle, but let’s think of something more subtle, an api request that returns important data to be used in our application. Imagine that this request will take 2 seconds to be completed. Will this request block the main thread for 2 seconds preventing any other code from being executed?
Event Loop
JavaScript has a runtime model based on event loop that permits the execution of asynchronous tasks, which are meant to run code that can potentially block the main-thread of execution like an API request. An event loop is a design pattern that waits and dipatches events that might trigger an execution. The JavaScript event loop runtime has the following steps of execution:
-
Call Stack:
Stack of synchronous tasks or events that had been already resolved in the events queue. -
Events Table:
Table that stores asynchronous tasks to be resolved by the internal Web Api. -
Events Queue:
FIFO Queue of resolved events. Resolved events are moved to Call Stack to be executed.
Here follows a flowchart of JavaScript event loop execution, to better explain the whole flow of the loop:

Callbacks
Developers should be aware of when a code is meant to be executed asynchronously. Any intense execution of tasks such as databases queries or API requests that takes time to retrive a response will block the main thread if not handled well. To address this issue, developers have to take advantage of events such as timers and callback functions.
A callback function is just an ordinary function that will be executed after a higher priority function has finished the execution. The higher priority function receives the callback function as an argument and thus, this callback can be executed inside the higher priority function closure. Let’s have a look in s simple example that emulates fetching a data from a server, for instance:
function fetchData(callback) {
// Fetching data "from a server"
setTimeout(() => {
const data = { name: "John", age: 30 };
callback(data);
}, 2000);
}
function synchronousFunction() {
console.log('Synchronours Function Executed')
}
function callBackFunction(data) {
console.log('Callback Data: ', data)
}
synchronousFunction()
fetchData(callBackFunction)
console.log('After synchronous function')
Output:
Synchronours Function Executed
After synchronous function
// after a while:
Callback Data:
Object { name: "John", age: 30 }
In the example above the fetchData function does not block the execution of the next console.log bacause it runs asynchronously. This is achieved because the callback will only be executed after the setTimeout timer has been handled by the internal Web API in the events table.
If we did not have included the setTimout function, the callback would be executed synchronously, imediately after the higher priority function have been executed, we’ve only have done that to emulate a data fetch. An actual fetch function would register an actual an event such as onload to also be handled by the internal Web API of the JavaScript runtime. The callback function would only be executed after the onload event had been resolved.
In summary, to run a function asynchronously in JavaScript the developer should register an event that will execute a callback after the event has been completed. Events are defined by the runtime, that might differs from the browser or server runtime. For instance, events on the browser are majorly associated to the DOM while events in NodeJS are commonly related to I/O operations.
Callback Hell
The issue with callbacks is when we have dependecies that arise from nested callbacks. The resulting code is not much readable as we may noticed in the simple example bellow:
setTimeout(() => {
console.log('First operation');
setTimeout(() => {
console.log('Second operation');
setTimeout(() => {
console.log('Third operation');
setTimeout(() => {
console.log('Fourth operation');
}, 2000);
}, 2000);
}, 2000);
}, 2000);
Each function will only be executed after their higher priority function creating a dependency of each of those where the Fourth operation depends of the first, and everything in between. Luckly we’re already in the future! JavaScript have evolved a lot and since the release of ECMAScript6 (ES6) in 2015, JavaScript was introducted to Promises that provides a standardized pattern to handle asynchronous functions.
Promises
Providing a more readable and flexible pattern for writing asynchronous code, promises uses chainded functions to deal with dependency execution. And also, they provide a better approach to handle errors in the code since it provide a catch method to inform when something wrong had occurred while in callbacks we had to provide our own error handlers for each nested function. We’re not going deep into the promises subject here but, just to understand a little more about it, let’s improve our earlier callback hell function with promises.
function executeAfterTime(str, time = 2000) {
return new Promise(resolve => setTimeout(() => {
resolve(console.log(str));
}, time));
}
async function awaitPromises() {
await Promise.all([
executeAfterTime("First operation"),
executeAfterTime("Second operation"),
executeAfterTime("Third operation"),
executeAfterTime("Fourth operation"),
]);
}
awaitPromises().then(() => console.log("All done!"));
Instead of nested setTimeout functions, we define executeAfterTime function that returns a new Promise that will be executed after 2 seconds. Then, we define a async function called awaitPromises that will await for all the promises in the array passed to Promises.all() to be executed. Finally we call the awaitPromises function and then, after the execution of all the promises we run another function to remind us that all promises have been resolved.
That’s it for the event loop. But, can you guess the order of execution of the code below?
const timeOutId = setTimeout(() => console.log('1'), 3000)
console.log('2')
function runAfterTimeout(cb) {
cb()
}
runAfterTimeout(() => console.log('3'))
function cbFunction(cb) {
setTimeout(() => {console.log('4')}, 2000)
cb()
}
runAfterTimeout(() => cbFunction(() => console.log('5')))
console.log('6')
You might want to take a look at the flowchart above to figure out when an event will be sent to the events table and which priority the events will have to answer the question.
References
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Event_loop
https://www.digitalocean.com/community/tutorials/how-to-use-multithreading-in-node-js
https://en.wikipedia.org/wiki/List_of_concurrent_and_parallel_programming_languages