Coming from the world of PHP, Node.js was a frustrating experience, especially when it came to event loops, callbacks, promises, and so on. It took me a few hours to understand the fundamentals of the Node.js event loop. So, for everyone else out there (who doesn't understand), I'll try to explain it as simply as I can.
As the official docs say
What is the Event Loop?
The event loop is what allows Node.js to perform non-blocking I/O operations — despite the fact that JavaScript is single-threaded — by offloading operations to the system kernel whenever possible.
Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background. When one of these operations completes, the kernel tells Node.js so that the appropriate callback may be added to the poll queue to eventually be executed.
When Node.js starts, it initializes a single thread known as the event loop. Node.js employs the event loop to handle asynchronous operations within applications.
An example of how an event loop works
1. Check to see if any of the timer functions listed below have been completed -
- SetTimeout
- SetInterval
- SetImmediate
2. Examine whether any outstanding OS tasks have been completed. As an example, consider an HTTP server that is listening on a specific port.
3. Check to see if long-running operations have been completed.
All of the above operations are tracked internally by Node.js. For the sake of simplicity, let us assume it keeps three different arrays for tracking operations such as pendingTimeouts, pendingOSTasks, and pendingCallbacks. (Note: These variables are named at random for clarity.) Node.js adds the operation and its callback to the relevant array for each operation performed.
For example, when a GET request is made to an external resource, Node.js adds it to the pendingOSTasks array. When a response is received, the associated callback is executed, and the event is removed from the array.
Node.js checks between event loop runs to see if it is waiting for any operations or timers and shuts down if there aren't any. In the preceding example, the decision is made by determining whether each array element is empty.
Actual steps/phases that occur within each Event loop
1. Timers: Callbacks scheduled by setTimeout() and setInterval() are executed during this phase. A timer specifies the threshold after which a provided callback may be executed rather than the exact time the callback should be executed. In theory, the poll phase determines when timers are executed.
2. Pending Callbacks: The majority of the callbacks will be processed here. The asynchronous I/O request is recorded in the queue, and the main call stack can resume normal operations. The I/O callbacks of completed or errored out I/O operations are processed in this phase of the Event Loop.
3. Idle: During this phase, the Event Loop executes any callbacks' internal operations. Technically, there is no direct influence on this phase or its duration. There is no mechanism in place to ensure code execution during this phase. It is primarily used for information gathering and planning of what needs to be done during the next tick of the event loop. To illustrate this phase, consider how a chef might begin preparing the ingredients and equipment needed to prepare the dessert course while the main course is still in the oven. As a customer, you don't need to be aware of what's going on as long as your food is served in the correct order and on time.
4. Polling: This is the phase in which all JavaScript code is executed, beginning at the top of the file and working down. Depending on the code, it may execute immediately or queue something to be executed during a future tick of the Event Loop. During this phase, the Event Loop manages the I/O workload by calling functions in the queue until the queue is empty and calculating how long it should wait before proceeding to the next phase. All callbacks in this phase are synchronously called in the order in which they were added to the queue, from oldest to newest. This is the phase in which our application may be stalled if any of these callbacks are slow or are not executed asynchronously. Please keep in mind that this phase is entirely optional. Depending on the state of your application, it may not occur on every tick. For example, if any setImmediate() timers are scheduled, Node.js will skip this phase during the current tick and move to the setImmediate() phase.
5. SetImmediate Callbacks: Node.js has a special timer, setImmediate(), and its callbacks are executed during this phase. This phase runs as soon as the poll phase becomes idle. If setImmediate() is scheduled during the I/O cycle, it will always be executed before any other timers, regardless of the number of timers present.
6. Close events: This phase handles all close event callbacks. This is the point at which the Event Loop has completed one cycle and is ready to move on to the next. It is primarily used to clean the application's state.
Blocking the event loop
Node.js sends time-consuming operations to the C++ API and its threads, such as I/O callbacks. This simulates "multithreading" within a single-threaded Node.js process, allowing the main runtime to continue executing our code without stopping. This provides Node.js with the advantages of an asynchronous non-blocking I/O interface.
Conclusions
- To avoid the complexity of writing multithreaded code, Node.js processes are single-threaded. The Event Loop, on the other hand, allows I/O tasks to be offloaded to C++ APIs. This allows the Node.js core runtime to continue running JavaScript code while the C++ APIs handle asynchronous I/O operations in the background. This assists in the development of non-blocking code.
- Timers, I/O callbacks, idle phase, polling, setImmediate callbacks execution, and close events callbacks are the six main phases of the Event Loop.
- When a phase is completed, the application advances to the next tick, and all phases are repeated, beginning with timers, until there is nothing left to process.
Comments
Post a Comment