Node.js Tutorial - 42 - Event Loop

Codevolution
13 Jan 202314:21

Summary

TLDRThis video explains the event loop in Node.js, highlighting its role in asynchronous code execution. The speaker reviews key concepts such as JavaScript's synchronous, single-threaded nature and the importance of libuv in handling asynchronous tasks. The video provides an in-depth walkthrough of synchronous and asynchronous code execution, followed by a visual representation of the event loop. It covers how different queues (timer, I/O, check, close, and microtask) are prioritized and executed, helping viewers understand Node.js's internal processes for managing async code efficiently.

Takeaways

  • 😀 JavaScript is a synchronous, blocking, single-threaded language, but asynchronous programming is enabled through external libraries like libuv.
  • 💡 The V8 engine, which executes JavaScript, consists of a memory heap for variable storage and a call stack for function execution.
  • 🛠️ When an async function is called, it's offloaded to libuv, which handles it using the system's async mechanisms or a thread pool, keeping the main thread free.
  • 📜 In synchronous code execution, functions are pushed onto the call stack and popped off in a last-in-first-out order, producing a sequential output.
  • ⏳ In asynchronous code, the async function is offloaded, and JavaScript continues executing other tasks while waiting for the async operation to finish.
  • 🔄 The Node.js event loop is a key mechanism that manages the execution of async callbacks once the call stack is empty.
  • 📝 There are multiple queues in the event loop, including the timer queue (setTimeout, setInterval), IO queue (async operations like file reads), check queue (setImmediate), close queue, and two microtask queues (nextTick, promise).
  • ⏱️ The event loop processes these queues in a specific priority order, with microtasks being executed first, followed by timer callbacks, IO callbacks, and others.
  • 🔀 Even if two async tasks complete at the same time (e.g., setTimeout and FS.readFile), timer callbacks are prioritized over IO callbacks.
  • 📊 The event loop continues to run as long as there are callbacks to process, and exits when there are none left, ensuring efficient asynchronous execution in Node.js.

Q & A

  • What is the primary role of the event loop in Node.js?

    -The event loop in Node.js orchestrates the execution of synchronous and asynchronous code. It ensures that tasks such as callbacks from async operations are handled appropriately and in a specific order after synchronous tasks have completed.

  • How does JavaScript handle asynchronous operations in Node.js?

    -JavaScript offloads asynchronous operations to a library called libuv in Node.js. The async task is then executed either using native OS mechanisms or through a thread pool, ensuring the main thread is not blocked.

  • What happens to synchronous code in Node.js?

    -Synchronous code in Node.js is executed in a straightforward, last-in-first-out manner on the call stack. Functions are pushed onto the stack and popped off once executed, with global scope execution starting first.

  • What is the role of the call stack and the heap in the V8 engine?

    -The call stack is responsible for tracking the execution of functions in a last-in, first-out manner. The heap is where memory is allocated for variables and functions during the execution of code.

  • How does the event loop prioritize execution of different callback functions?

    -The event loop executes callbacks in a specific order: first, callbacks in the microtask queues (next tick queue and promise queue), followed by callbacks in the timer queue (for setTimeout and setInterval), then the I/O queue, check queue, and close queue. Microtask queues are rechecked after every other callback queue.

  • What is the difference between the timer queue and the I/O queue in Node.js?

    -The timer queue handles callbacks associated with setTimeout and setInterval, while the I/O queue handles callbacks related to asynchronous I/O operations, such as reading files with fs.readFile.

  • What are microtask queues, and why are they important?

    -Microtask queues consist of the next tick queue (for process.nextTick callbacks) and the promise queue (for native promises). They are crucial because callbacks in these queues are given the highest priority and are executed before any other async tasks.

  • What happens if two async tasks, such as setTimeout and fs.readFile, complete simultaneously?

    -If setTimeout and fs.readFile complete at the same time, the callback from the timer queue (setTimeout) is executed first, followed by the callback from the I/O queue (fs.readFile). This priority order is part of how the event loop manages task execution.

  • When does Node.js execute callbacks for asynchronous tasks?

    -Node.js executes callbacks for asynchronous tasks only when the call stack is empty, ensuring that no synchronous code execution is interrupted.

  • What are some node-specific functions mentioned in the script?

    -Node-specific functions mentioned in the script include setImmediate (which places callbacks in the check queue) and process.nextTick (which places callbacks in the next tick queue, a part of the microtask queues).

Outlines

00:00

🔄 Understanding Async Code Execution in Node.js

This section introduces the concept of asynchronous (async) code execution in Node.js, emphasizing that JavaScript is inherently synchronous, single-threaded, and blocking. It explains how async programming is made possible with the help of libraries like Libuv. The paragraph also details how code executes within the Node.js runtime, including the role of the V8 engine in memory allocation (Heap) and execution (Call Stack), and how Libuv offloads async tasks to the operating system’s native mechanisms or thread pool to ensure the main thread remains unblocked. Through a simple synchronous code example, the step-by-step execution of functions is illustrated.

05:02

📝 Comparing Synchronous and Asynchronous Code Execution

This part dives deeper into how Node.js handles async operations. By contrasting a synchronous code snippet with an asynchronous example (using `fs.readFile`), it explains how async tasks are handed over to Libuv for processing and how callbacks are executed after the main code completes. The explanation visualizes how the call stack manages both sync and async code and shows how the console outputs 'first', 'third', and 'second' in an asynchronous operation. It concludes with questions regarding the precise execution order of async callbacks, hinting at the complexity of callback execution timing and priorities.

10:02

🔁 The Event Loop: Coordinating Sync and Async Code

This section introduces the concept of the event loop, which is central to how Node.js handles asynchronous callbacks. It explains that the event loop coordinates the execution of both sync and async code, running through various queues that hold callbacks of different types (e.g., timer callbacks, I/O callbacks). The visual representation of the event loop is described in detail, covering six key queues: Timer, I/O, Check, Close, and two microtask queues (Next Tick Queue and Promise Queue). The microtask queues, specific to Node.js, are highlighted as distinct from the others, playing a crucial role in callback execution order.

Mindmap

Keywords

💡Event Loop

The event loop is a core component in Node.js that coordinates the execution of synchronous and asynchronous code. It ensures non-blocking execution by cycling through different queues and pushing callbacks onto the call stack when appropriate. The video explains that understanding the event loop is key to mastering async programming in Node.js, as it determines when and how tasks like I/O operations and timer callbacks are executed.

💡Call Stack

The call stack is a fundamental part of the JavaScript engine, responsible for keeping track of function calls in a last-in, first-out (LIFO) manner. In the video, it's described as the place where synchronous code is executed, and async functions are added or removed as their execution completes. This structure allows the event loop to prioritize when callbacks are executed after the stack becomes empty.

💡Async Programming

Asynchronous programming allows Node.js to perform tasks without blocking the main thread, enabling operations like I/O to run in the background. The video emphasizes that async programming in Node.js requires understanding how async tasks are handled by the event loop, libuv, and other related mechanisms to avoid performance bottlenecks.

💡libuv

Libuv is a multi-platform library that Node.js uses to handle asynchronous I/O operations. It provides the thread pool for non-blocking I/O and manages async callbacks via the event loop. The video explains how libuv offloads tasks like reading files to threads so that the main event loop is free for other operations.

💡Timers Queue

The timers queue holds callback functions for `setTimeout` and `setInterval` in Node.js. These callbacks are executed when their timer expires, as explained in the video. The event loop prioritizes these callbacks at specific points in its cycle, running them only when the call stack is clear.

💡Microtask Queue

The microtask queue holds high-priority tasks such as `process.nextTick` and promise callbacks. According to the video, microtasks are always executed first during the event loop cycle, even before callbacks from timers or I/O operations, making them crucial for fast async code execution.

💡I/O Queue

The I/O queue is where callbacks related to asynchronous I/O operations, such as file reads and network requests, are placed. The video highlights that after the timers and microtask queues are processed, Node.js moves to the I/O queue to handle operations like those from the `fs` and `http` modules.

💡setImmediate

A Node.js-specific function that schedules a callback to be executed immediately after the I/O phase in the event loop. The video describes `setImmediate` callbacks as being placed in the 'check queue,' which is processed right after the I/O callbacks but before timers in the next loop iteration.

💡Synchronous Code

Synchronous code is executed line by line, blocking the execution of other tasks until it finishes. The video contrasts this with asynchronous code, showing how synchronous functions are processed directly on the call stack, while async operations are offloaded to different queues handled by the event loop.

💡Callback Functions

Callback functions are passed to asynchronous operations, allowing code to be executed after the completion of a task, such as a file read or timer. In the video, the execution order of callbacks in different queues (timers, I/O, microtasks) is carefully explained as they relate to how the event loop functions.

Highlights

JavaScript is a synchronous, blocking, single-threaded language, making async programming essential for non-blocking operations.

Node.js achieves asynchronous programming using the V8 engine and libuv, with the latter handling async tasks and ensuring non-blocking of the main thread.

The V8 engine manages memory and a call stack, executing synchronous code in a last-in, first-out manner.

Libuv is responsible for executing async operations by offloading tasks to the operating system's native async mechanisms or using its thread pool.

Synchronous code execution follows a clear pattern where functions are pushed to and popped off the call stack, with no interruptions or delays.

Asynchronous operations like 'fs.readFile' are offloaded to libuv, allowing the call stack to proceed with other tasks while libuv processes the async task.

The event loop in Node.js is a core design pattern that coordinates the execution of synchronous and asynchronous code, ensuring efficient handling of tasks.

There are six different queues in the event loop: Timer queue, IO queue, Check queue, Close queue, and two microtask queues (Next Tick and Promise).

The priority order for callbacks within the event loop is crucial, with synchronous code always taking precedence over async operations.

Microtask queues, including Next Tick and Promise queues, have the highest priority, and are executed first when the call stack is empty.

Timers (setTimeout and setInterval) have priority over IO callbacks in the event loop, even if both are ready to be executed at the same time.

Set Immediate is specific to Node.js and adds tasks to the Check queue, executed after IO callbacks but before the Close queue.

The event loop continues running as long as there are tasks to process. If all callbacks are executed and no code remains, the loop exits.

The event loop's orchestration of tasks ensures non-blocking performance, answering questions like when async callbacks are executed.

Understanding the event loop helps clarify the execution priority of different async methods and how they interact with the call stack in Node.js.

Transcripts

play00:04

welcome back in this video Let's

play00:07

understand about the all-important event

play00:10

Loop in node.js

play00:12

let me Begin by reiterating a few points

play00:15

about async code execution

play00:18

first

play00:19

JavaScript is a synchronous blocking

play00:22

single threaded language

play00:25

second

play00:26

to make async programming possible we

play00:28

need the help of Liberty I hope this is

play00:32

clear to you

play00:33

using these two points let me now paint

play00:36

a picture in your mind as to how code

play00:38

typically executes in the node runtime

play00:42

on the left we have the V8 engine which

play00:45

executes JavaScript code

play00:48

it comprises of a memory he and a call

play00:51

stack

play00:52

whenever you declare variables or

play00:54

functions memory is allocated on the

play00:57

Heap

play00:58

whenever you execute code functions are

play01:01

pushed into the call stack and when the

play01:03

function returns it is popped off the

play01:05

call stack

play01:07

straightforward last in first out

play01:09

implementation of the stack data

play01:11

structure

play01:12

on the right we have libue

play01:15

whenever you execute an async method it

play01:18

is offloaded to the UV

play01:20

Liberty will then run the task using

play01:23

native async mechanisms of the operating

play01:25

system and if that is not possible it

play01:28

will utilize its thread pool to run the

play01:30

task ensuring the main thread is not

play01:33

blocked

play01:35

let's now walk through two simple code

play01:37

Snippets and understand how the V8

play01:40

engine and libue are used by node

play01:45

first let's take a look at synchronous

play01:48

code execution

play01:49

on the left we have a simple code

play01:51

snippet

play01:52

three console log statements that log

play01:55

first second and third one after the

play01:58

other

play01:59

let's now walk through the code as if

play02:01

the runtime is executing it

play02:04

the main thread of execution always

play02:06

starts in the global scope

play02:08

so the global function if you can call

play02:10

it that is pushed onto the stack

play02:13

then on line 1 we have a console log

play02:16

statement

play02:18

the function is pushed onto the stack

play02:20

and for the sake of understanding the

play02:22

timeline let's assume this happens at

play02:25

one millisecond

play02:26

first is logged to the console

play02:29

then the function is popped off the

play02:32

stack

play02:33

execution comes to line two

play02:36

let's say at 2 milliseconds

play02:38

log function is again pushed onto the

play02:41

stack

play02:43

second is locked to the console and the

play02:45

function is popped off the stack

play02:48

finally execution is on line three and

play02:51

at three milliseconds function is pushed

play02:54

onto the stack

play02:55

third is locked to the console

play02:58

and the function is popped off the stack

play03:01

there is no more code to execute and

play03:03

Global is also popped off

play03:06

this is pretty much how synchronous code

play03:09

execution can be visualized with the

play03:11

node runtime

play03:14

next let's take a look at async nescode

play03:17

execution

play03:18

on the left we have another code snippet

play03:22

three log statements like before but

play03:24

this time the second log statement is

play03:27

within a callback function passed to FS

play03:29

dot read file

play03:32

let us once again walk through the code

play03:34

as if the runtime is executing it

play03:38

the main thread of execution always

play03:40

starts in the global scope so the global

play03:42

function is pushed onto the stack

play03:45

execution comes to line one at one

play03:48

millisecond console.log is pushed onto

play03:51

the stack

play03:52

first is logged in the console

play03:55

and the function is popped off the stack

play03:58

execution now moves on to line two

play04:02

add 2 milliseconds the read file method

play04:05

gets pushed onto the stack

play04:07

in the earlier lecture I mentioned that

play04:09

read file is an async operation that is

play04:12

offloaded to libue

play04:14

so what happens now is that the Callback

play04:17

function is handed over to libue

play04:20

JavaScript then simply pops off the

play04:23

read5 method from the call stack because

play04:25

its job is done as far as execution of

play04:28

line 2 is concerned

play04:30

in the background the beauty starts to

play04:33

read the file contents on a separate

play04:35

thread

play04:37

at 3 milliseconds JavaScript proceeds to

play04:40

line 5. it pushes the log function onto

play04:43

the stack

play04:45

third gets locked to the console

play04:47

and the function is popped off the stack

play04:51

now there is no more user written code

play04:53

in the global scope to execute so the

play04:56

call stack is empty

play04:58

at about 4 milliseconds let's say that

play05:01

the file read task is completed in the

play05:03

thread pool

play05:04

the associated callback function

play05:07

is now pushed onto the call stack

play05:10

within the Callback function we have the

play05:12

log statement

play05:14

that is pushed onto the call stack

play05:17

second is locked to the console and the

play05:19

function is popped off

play05:22

as there are no more statements to

play05:24

execute in the Callback function

play05:26

that is popped off as well

play05:28

no more code to run so the global

play05:31

function as we call it is also popped

play05:33

off the stack

play05:35

the console output is going to read

play05:37

first third and then second

play05:40

this is how the node runtime executes an

play05:44

asynchronous code snippet that uses FS

play05:47

dot read file

play05:49

similar execution holds good for other

play05:51

async methods as well

play05:54

I hope it is clear to you as to how code

play05:56

is executed in the node runtime

play06:00

now if you've understood this far

play06:03

let me ask you a few questions

play06:06

whenever an async task completes in liby

play06:09

at what point does node decide to run

play06:12

the associated callback function on the

play06:15

call stack

play06:16

does it wait for the call stack to be

play06:18

empty or does it interrupt the normal

play06:20

flow of execution to run the Callback

play06:23

function

play06:24

what about async methods like set

play06:26

timeout and set interval which also

play06:29

delay the execution of callback function

play06:33

F2 async tasks such as set timeout and

play06:36

Fs dot read file complete at the same

play06:38

time how does node decide which callback

play06:41

function to run first on the call stack

play06:44

does 1 get priority over the other

play06:47

at the moment we simply don't know

play06:51

just when we thought we understood how

play06:53

code is executed behind the scenes in

play06:55

the node runtime it seems to have become

play06:58

more complex

play06:59

well let me tell you all these questions

play07:02

can be answered by understanding about

play07:04

the core part of liby which is the event

play07:07

Loop

play07:08

now what is the event Loop well

play07:10

technically it is just a c program

play07:13

but you can think of event loop as a

play07:16

design pattern that orchestrates or

play07:19

coordinates the execution of synchronous

play07:21

and asynchronous code in node.js

play07:24

and the way we are going to understand

play07:26

how the event Loop works is a two-step

play07:29

process

play07:30

in this video we will take a look at a

play07:33

visual representation of the event Loop

play07:35

that will give you a brief overview of

play07:38

the different parts that come together

play07:39

in the event Loop

play07:41

and then over the next few videos we

play07:44

will conduct various experiments with

play07:46

code to better understand the visual

play07:48

representation

play07:52

all right here is how we can visualize

play07:55

the event Loop

play07:57

I would like to pause and thank deepal

play07:59

jsekara who has written an article where

play08:02

I first came across a similar

play08:03

representation

play08:05

it has been very useful for me and now

play08:07

my job is to make sure you easily

play08:09

understand this as well

play08:12

now the event Loop is a loop that is

play08:14

alive as long as your node.js

play08:16

application is up and running

play08:19

in every iteration of the loop we come

play08:21

across six different cues

play08:24

each queue holds one or more callback

play08:27

functions that need to be eventually

play08:29

executed on the call stack

play08:31

and of course the type of callback

play08:34

functions are different for each queue

play08:38

first we have the timer queue this

play08:41

contains callbacks associated with set

play08:44

timeout and set interval

play08:47

second we have the ioq

play08:50

this contains callbacks associated with

play08:52

all the async methods that we have seen

play08:54

so far

play08:55

example methods associated with the fs

play08:58

and HTTP modules

play09:01

third we have the check queue now this

play09:04

contains the callbacks associated with a

play09:07

function called set immediate

play09:09

this function is specific to node and

play09:12

it's not something you would come across

play09:13

when writing JavaScript for the browser

play09:16

fourth we have the close queue this

play09:19

contains callbacks associated with the

play09:21

close event of an async task

play09:24

finally we have a micro task Queue at

play09:27

the center

play09:28

this is actually two separate cues

play09:32

the first queue is called Next tick

play09:34

queue and contains callbacks associated

play09:37

with a function called process dot next

play09:39

take which is again specific to node.js

play09:44

the second cue is the promise queue

play09:46

which contains callbacks that are

play09:48

associated with the native promise in

play09:50

JavaScript

play09:52

and one very important point to note is

play09:55

that timer IO check and close queues are

play09:59

all part of libuby

play10:02

the two microtask use however are not

play10:05

part of libuee

play10:07

hopefully the color difference conveys

play10:09

that

play10:10

nevertheless there's still part of the

play10:12

node runtime and play an important role

play10:15

in the order of execution of callbacks

play10:18

speaking of which let's understand that

play10:20

next

play10:21

the arrowheads are already a giveaway

play10:24

but it is very easy to get confused

play10:28

so let me explain the priority order of

play10:30

the queues

play10:33

first you should know that all user

play10:36

written synchronous JavaScript code

play10:38

takes priority over asynchronous code

play10:40

that the runtime would like to execute

play10:43

which means only after the call stack is

play10:46

empty the event Loop comes into picture

play10:50

within the event Loop though the

play10:53

sequence of execution follows certain

play10:55

rules and I will warn you there are

play10:57

quite a few rules you have to wrap your

play10:59

head around

play11:00

let's go over them one at a time

play11:04

step one

play11:05

any callbacks in the micro task queues

play11:08

are executed

play11:10

first tasks in the next queue and only

play11:13

then tasks in the promise queue

play11:16

Step 2 all callbacks within the timer

play11:19

queue are executed

play11:22

step 3 callbacks in the micro task

play11:24

queues if present are executed after the

play11:28

execution of every callback in the timer

play11:30

queue

play11:31

again first tasks in the next queue and

play11:35

then tasks in the promise queue

play11:38

step 4

play11:39

all callbacks within the ioq are

play11:42

executed

play11:44

step 5 callbacks in the micro task

play11:47

queues if present are executed

play11:50

next to queue followed by promise queue

play11:54

step 6 all callbacks in the check queue

play11:57

are executed

play11:59

step 7 callbacks in the micro task

play12:02

queues if present are executed after the

play12:05

execution of every callback in the check

play12:08

queue

play12:09

again first tasks in the next IQ and

play12:12

then tasks in the promise queue

play12:15

step 8 all callbacks in the close queue

play12:19

are executed

play12:21

step 9

play12:22

for one final time in the same Loop the

play12:25

micro task queues are executed next a

play12:29

queue followed by promise queue

play12:32

at this point if there are more

play12:35

callbacks to be processed the loop is

play12:38

kept alive for one more run and the same

play12:41

steps are repeated

play12:43

on the other hand

play12:45

if all callbacks are executed and there

play12:47

is no more code to process the event

play12:50

Loop exits

play12:52

this is the role libuey's event Loop

play12:55

plays in the execution of async code in

play12:57

node.js

play12:59

I'm also hopeful that we now have

play13:01

answers to the questions we had a few

play13:03

minutes ago

play13:05

for our first question the answer is

play13:08

that callback functions are executed

play13:10

only when the call stack is empty the

play13:13

normal flow of execution will not be

play13:16

interrupted to run a callback function

play13:20

for our second question we now know that

play13:23

set timeout and set interval callbacks

play13:25

are given first priority

play13:29

for the third question We Now understand

play13:31

that timer callbacks are executed before

play13:34

I O callbacks even if both are ready at

play13:37

the exact same time

play13:40

apart from this we also learned about a

play13:42

few other cues that have their own

play13:44

priority

play13:47

but this visual representation here is

play13:50

what I want you to imprint in your mind

play13:52

as it is how node executes async code

play13:55

Under The Herd

play13:57

if it is now clear as to what the event

play13:59

Loop is over the next few videos let's

play14:02

conduct a few different experiments to

play14:04

understand and verify the order of

play14:07

execution in the event Loop

play14:09

thank you for watching please do

play14:11

consider subscribing to the channel and

play14:13

I'll see you in the next one

Rate This

5.0 / 5 (0 votes)

Etiquetas Relacionadas
Node.jsAsync ProgrammingEvent LoopCallback FunctionsJavaScriptLibuvCoding TutorialV8 EngineTech ExplainerWeb Development
¿Necesitas un resumen en inglés?