Book Image

Modern Frontend Development with Node.js

By : Florian Rappl
5 (1)
Book Image

Modern Frontend Development with Node.js

5 (1)
By: Florian Rappl

Overview of this book

Almost a decade after the release of Node.js, the tooling used by frontend developers is fully embracing this cross-platform JavaScript runtime, which is sadly often limited to server-side web development. This is where this Node.js book comes in, showing you what this popular runtime has to offer and how you can unlock its full potential to create frontend-focused web apps. You’ll begin by learning the basics and internals of Node.js, before discovering how to divide your code into modules and packages. Next, you’ll get to grips with the most popular package managers and their uses and find out how to use TypeScript and other JavaScript variants with Node.js. Knowing which tool to use when is crucial, so this book helps you understand all the available state-of-the-art tools in Node.js. You’ll interact with linters such as ESLint and formatters such as Prettier. As you advance, you’ll become well-versed with the Swiss Army Knife for frontend developers – the bundler. You’ll also explore various testing utilities, such as Jest, for code quality verification. Finally, you’ll be able to publish your code in reusable packages with ease. By the end of this web development book, you’ll have gained the knowledge to confidently choose the right code structure for your repositories with all that you’ve learned about monorepos.
Table of Contents (17 chapters)
1
Part 1: Node.js Fundamentals
5
Part 2: Tooling
10
Part 3: Advanced Topics

Understanding the event loop

An event loop is a runtime model that enables users to run all operations from a single thread – irrespective of whether the operations access long-running external resources or not. For this to work, the event loop needs to make requests to an event provider, which calls the specified event handlers. In Node.js, the libuv library is used for event loop implementation.

The reason for giving libuv the most space in Figure 1.1 is to highlight the importance of this library. Internally, libuv is used for everything regarding I/O, which arguably is the most crucial piece of any framework. I/O lets a framework communicate with other resources, such as files, servers, or databases. By default, dealing with I/O is done in a blocking manner. This means that the sequence of operations in our application is essentially stopped, waiting for the I/O operation to finish.

Two strategies for mitigating the performance implications of blocking I/O exist.

The first strategy is to create new threads for actually performing these blocking I/O operations. Since a thread contains an independent group of operations, it can run concurrently, eventually not stopping the operations running in the original thread of the application.

The second strategy is to not use blocking I/O at all. Instead, use an alternative variant, which is usually called non-blocking I/O or asynchronous I/O. Non-blocking I/O works with callbacks, that is, functions that are called under certain conditions – for instance when the I/O operation is finished. Node.js uses libuv to make extensive use of this second strategy. This allows Node.js to run all code in a single thread, while I/O operations run concurrently.

In Figure 1.3, the building blocks of libuv are displayed. The key part is that libuv already comes with a lot of functionality to handle network I/O. Furthermore, file and DNS operations are also covered well:

Figure 1.3 – Building blocks of libuv

Figure 1.3 – Building blocks of libuv

In addition to the different I/O operations, the library comes with a set of different options for handling asynchronous user code.

The event loop itself follows the reactor design pattern. Wikipedia describes the pattern as follows:

The reactor design pattern is an event handling pattern for handling service requests delivered concurrently to a service handler by one or more inputs. The service handler then demultiplexes the incoming requests and dispatches them synchronously to the associated request handlers. (https://en.wikipedia.org/wiki/Reactor_pattern)

Importantly, this definition mentions synchronous dispatch. This means that code that is run through the event loop is guaranteed to not run into any conflicts. The event loop makes sure that code is always run sequentially. Even though the I/O operations may concurrently run, our callbacks will never be invoked in parallel. From our perspective, even though Node.js will internally (through libuv) use multiple threads, the whole application is single-threaded.

The following is a simple script that shows you the basic behavior of the event loop at play – we’ll discuss how to run this in the Using Node.js from the command line section:

events.js

console.log('A [start]');
setTimeout(() => console.log('B [timeout]'), 0);
Promise.resolve().then(() => console.log('C [promise]'));
console.log('D [end]');

We will run this script in the next section when we learn about the command line usage of Node.js. In the meantime, put some thought into the preceding code and write down the order in which you’ll see the console output. Do you think it will print in an “A B C D” order, or something else?

The algorithm of the implementation of the event loop in libuv is displayed in Figure 1.4:

Figure 1.4 – The implementation of the event loop in libuv

Figure 1.4 – The implementation of the event loop in libuv

While the code snippet only deals with JavaScript-related constructs (such as console, Promise, and setTimeout), in general, the callbacks are associated with resources that go beyond Node.js, such as file system changes or network requests. Some of these resources may have an operating system equivalent; others only exist in form of blocking I/O.

Consequently, the event loop implementation always considers its thread pool and polls for progressed I/O operations. Timers (such as setTimeout in the example script) are only run in the beginning. To know whether a timer needs to be run, its due time is compared with the current time. The current time is synced with the system time initially. If there is nothing to be done anymore (that is, no active timer, no resource waiting to finish, etc.), then the loop exits.

Let’s see how we can run Node.js to solidify our knowledge about the event loop.