Hello and welcome to the new blog,
Let's begin today's story in this blog we will learn about event loop the most asked interview question and quite important thing to learn in Nodejs
Event loop is a single-threaded system, because of which Node.js works as a single thread and handles multiple API calls, achieving concurrency being a single-threaded runtime module.
For newcomers, single-threaded is like one method at a time, one connection at a time, one thread at a time, one class instance at a time. Because of the Event Loop Node or JS can run multiple threads concurrently (in parallel)
We will start with the basic code given below
//This file explains the event loop class object under the hood
const callStacks = [];
const callbackQueues = [];
class EventLoop {
constructor() {
this.running = false;
this.microTasks = [];
this.macroTasks = [];
}
setTask(task) {
this.macroTasks.push(task);
}
setMicroTask(task) {
this.microTasks.push(task);
}
run() {
if (this.running) return;
this.running = true;
const loop = () => {
while (this.microTasks.length > 0) {
const micro = this.microTasks.shift();
micro();
}
while (this.macroTasks.length > 0) {
const task = this.macroTasks.shift();
task();
}
};
loop();
}
}In the above method, we have defined 2 arrays and one variable in the constructor(means each constructor or variable or a method or an object is defined and initialised and can be accessed using this.self) and 2 methods such as setTask and setMicroTask and lastly a run method to run the Event Loop.
2 database structures are always used when the Event Loop is defined: the callstack and the queue; it depends entirely on the callstack and on queue.
Queue: First in, First out, simple queue like we have a samosa shop line at sharp 10 AM
Callstack: Reverse of Queue, First in Last Out or Last In First Out
It's important to understand them as we will use them later on.
How the Event Loop Works
Quite simple, every JavaScript method can be of 3 types
- Simple console.log or JS method
- Micro tasks, for example, Promise, Async/Await, API call
- Macro Tasks, for example, Web APIs like setTimeout and setInterval
Every event loop first runs the simple task, and it won't take much time to run all simple tasks, and then micro tasks and followed by macro tasks in the end.
So if we have Task 1 as a simple task and Task 2 as a micro task, and Task 3 as a macro task, then Node.js Event Loop will run or execute Task 1 -> Task 2 -> Task 3
In the code below,
class EventLoop {
constructor() {
this.running = false;
this.microTasks = [];
this.macroTasks = [];
}
setTask(task) {
this.macroTasks.push(task);
}
setMicroTask(task) {
this.microTasks.push(task);
}
run() {
if (this.running) return;
this.running = true;
const loop = () => {
while (this.microTasks.length > 0) {
const micro = this.microTasks.shift();
micro();
}
while (this.macroTasks.length > 0) {
const task = this.macroTasks.shift();
task();
}
};
loop();
}
}
We have defined a callstack, then a queue, and a variable to get the Event Loop running status and defined 2 2-task categorisation method for adding tasks either as a callstack or as a queue.
setTask will add the task to the queue
setMicroTask will add the task to the callstack
The run method will start the loop to execute the tasks in a specific fashion, empty all micro tasks first, followed by emptying the macro tasks queue or executing all macro tasks in the end.
console.log, Math methods, Promise and Async/Await are all micro tasks
Web APIs, for example, setTimeout, setInterval, setImmediate, Keyboard events, are all macro tasks
What happens if I run the below JS code
console.log("First task 1");
setTimeout(() => {
console.log("Second task 2");
}, 0);
console.log("Third task 3");
setTimeout(() => {
console.log("Second task 4");
}, 1);
console.log("Fifth task 5");
Output:
Now, one can understand how JS works or how the Event Loop works.
Let's bring more complications with real-world JS methods
What will be the output in the case below?
class EventLoop {
constructor() {
this.running = false;
this.microTasks = [];
this.macroTasks = [];
}
setTask(task) {
this.macroTasks.push(task);
}
setMicroTask(task) {
this.microTasks.push(task);
}
run() {
if (this.running) return;
this.running = true;
const loop = () => {
while (this.microTasks.length > 0) {
const micro = this.microTasks.shift();
micro();
}
while (this.macroTasks.length > 0) {
const task = this.macroTasks.shift();
task();
}
};
loop();
}
}
const loop = new EventLoop();
loop.setTask(() => {
Promise.resolve().then(() => console.log("Task 1 resolved"));
});
loop.setTask(() => {
console.log("macro task 2");
});
loop.setTask(() => {
setTimeout(() => {
console.log("micro task 1");
}, 0);
});
loop.setMicroTask(async () => {
const user = await fetch("https://jsonplaceholder.typicode.com/users/1");
return user.json();
});
loop.run();
Output:
First, it runs micro task 2 console, then it runs Task 2 Promise defined first, and then it runs micro task 1 console defined in line 3rd, and lastly it runs the API call fetching user data from dummy API.
So now the question is, does JS Event Loop run all API in the end? What happens is that Promise and the fetch API are mentioned together.
// this file explain event lopp class object under the hood
const callStacks = [];
const callbackQueues = [];
class EventLoop {
constructor() {
this.running = false;
this.microTasks = [];
this.macroTasks = [];
}
setTask(task) {
this.macroTasks.push(task);
}
setMicroTask(task) {
this.microTasks.push(task);
}
run() {
if (this.running) return;
this.running = true;
const loop = () => {
while (this.microTasks.length > 0) {
const micro = this.microTasks.shift();
micro();
}
while (this.macroTasks.length > 0) {
const task = this.macroTasks.shift();
task();
}
};
loop();
}
}
const loop = new EventLoop();
loop.setMicroTask(async () => {
const post = await fetch("https://jsonplaceholder.typicode.com/posts/1");
const postData = await post.json();
console.log(postData, "micro task 1");
return postData;
});
loop.setTask(() => {
Promise.resolve().then(() => console.log("Task 2 resolved"));
});
loop.setTask(() => {
console.log("macro task 2");
});
loop.setTask(() => {
setTimeout(() => {
console.log("micro task 1");
}, 0);
});
loop.setMicroTask(async () => {
const user = await fetch("https://jsonplaceholder.typicode.com/users/1");
const userData = await user.json();
console.log(userData, "micro task 3");
return userData;
});
loop.setTask(() => {
console.log("simple tasks")
})
loop.run();
Don't go into too much detail about numbers or setTask or setMicroTask; the interviewer will change the method name, and you will get confused. To avoid that situation, only understand the concept. Event Loop basically puts the methods either into the Queue or on the callstack, and starts by running all small methods from the callstack one by one, and finally it moves to executing and emptying the queue.
In the above method, we first make an API call, then put some Promise resolve and then one console, and lastly fetch another API call and the output is as given below
Output:
That's it for today, see you in the next one
iHateReading
Summary
This blog post explains the Node.js event loop, a crucial concept often asked in interviews. The event loop is single-threaded but achieves concurrency by handling multiple API calls sequentially. It uses two primary data structures: a call stack (LIFO) and a callback queue (FIFO). Tasks are categorized as simple tasks, microtasks (Promises, async/await), and macrotasks (setTimeout, setInterval). The event loop prioritizes simple tasks, then microtasks, and finally macrotasks. The post illustrates this with code examples, showing how different task types are processed and emphasizing the order of execution. It concludes by advising readers to focus on the underlying concepts of the call stack and callback queue rather than specific method names.
