Last time we were talking about CLI in
Node.js , let’s see how one of the essential parts of the technology works internally. The topic for this article is to overview the
Event Loop concept and see the program examples using the
Event-Driven applications design paradigm.
Event Loop is a program, which allows subscribing to system resource events. It provides a callback stack that can be linked to resources and executed when appropriate descriptors are ready.
Event-Driven paradigm, by attaching callbacks to user actions.
The example with timeouts shows the log order in a simple
Asynchronous operations are passed to the internal
Event Loop mechanism and scheduled to be executed at some point later. After given timeouts of 1000 and 2000 milliseconds, the
second callbacks are added to the
call stack on a
In a synchronous environment, without the
Event Loop, the output would follow the syntactic code order:
In traditional Web Server-side architecture, each call to
I/O operations is blocking the program’s main thread. In
Node.js when any
I/O event occurs, it schedules a task on the
libuv stack to process, and continues to run the
Node.js starts, a few things happen first:
- then the given script is executed with dependent libraries and synchronous calls (
API, setTimeout, process.nextTick),
- callbacks to async operations and timeouts are set,
- after which
Event Loopis waiting for events to happen.
After the execution of an initial script is done, the
Event Loop starts processing events. If there is nothing to be processed on the main thread, it is put to sleep, but it wakes up every time a system event happens or an operation completes, or a timer goes off. Finally, everything ends with the
Most operations in the
Node.js ecosystem exist in both synchronous and asynchronous ways. Each time an asynchronous operation is executed, the
Event Loop schedules a delayed callback.
Native modules using
Event Loop are:
- … and many others
event.js is planned to be read by the
Node.js environment, and the callback is attached to that action.
Libuv is a cross-platform asynchronous
I/O library, which implements
Event Loop, Timers, Sockets, File System operations, Signal handling, Child processes, and Threads.
It takes care of polling for
I/Oand scheduling callbacks to be run based on different sources of events.
The phases provided by
libuv are the following
Interestingly enough, the
libuv phases look recognizable in original code
timers- is a phase where callbacks scheduled by
pending callbacks- for almost all built-in functions callbacks, except
immediates, close types
poll- for connections
close- events like
Timeouts, Immediate, and process.nextTick()
setTimeout(), setInterval()functionality is almost similar to a browser
setImmediate()- special timers, executed during the
process.nextTick()- schedules a callback to execute inside phases
Phases Event Loop Example
Let’s overview what will happen with the following code example of different asynchronous
Node.js API calls
First of all, it will not work! 👋😀
process.nextTick() is recursively calling itself. It's the fastest execution of asynchronous code outside of
Event Loop. To be precise,
nextTick() callbacks are invoked after current synchronous call. The output will be infinite
next messages in the console. Let's fix it quickly
By running a program
node event-loop-fix-next.js we will see something like:
Let’s check why it happens 🕵🏾♂️👩💻
A short remark before we go to the console output
const fs = require('fs')
require() is a part of the
CommonJS module system. It's invocation is synchronous in a program's flow meaning, as well as other
APIs in the current example such as
setTimeout(), setImmediate(), fs.readFile(), process.nextTick(). Latter functions, however, schedule callbacks to be executed at some point in the future.
next message is produced by
nextTick() call, which is first, before going to the
Event Loop phase. The order of the next two messages can differ:
both variants are possible.
the order in which the two timers are executed is non-deterministic, as it is bound by the performance of the process
Next goes the
fs message regardless of whether the file
event.js exists in the file system or not. The callback will be invoked either with the first
error argument, if the file cannot be read or with the file data otherwise.
readFile() callback function body callbacks are attached similarly like before. The
next in message is the next one. Again,
process.nextTick()is the fastest execution of asynchronous code outside of
Event Loop, and it’s called at the end of the current phase. Which by the way, is the poll phase.
In the beginning, we saw a non-deterministic behavior of a program with
immediate out and
timeout out events. Now, on the contrary, the order is strictly defined. The
immediate in event is shown before
timeout in according to the
Event Loop phases diagram.
I’ve made a short video explaining this example:
Event Loop looks a bit familiar now!
The good news!
React.js technologies. It starts this June and we hope to see you there!
And have a nice coding time!