Node.js: qu'est-ce que c'est, quand et comment l'utiliser, et pourquoi vous devriez

Vous avez probablement déjà lu ces phrases…

Node.js est un moteur d'exécution JavaScript basé sur le moteur JavaScript V8 de Chrome.Node.js utilise un modèle d'E / S asynchrone non bloquant piloté par les événements

… Et se sont demandé ce que tout cela signifiait. J'espère qu'à la fin de cet article, vous aurez une meilleure compréhension de ces termes ainsi que de ce qu'est Node, comment il fonctionne, et pourquoi et quand est une bonne idée de l'utiliser.

Commençons par passer en revue la terminologie.

E / S (entrée / sortie)

Abréviation de input / output, I / O se réfère principalement à l'interaction du programme avec le disque et le réseau du système. Des exemples d'opérations d'E / S incluent la lecture / l'écriture de données depuis / vers un disque, la création de requêtes HTTP et la communication avec des bases de données. Ils sont très lents par rapport à l'accès à la mémoire (RAM) ou au travail sur le processeur.

Synchrone vs asynchrone

L' exécution synchrone (ou sync) fait généralement référence à un code s'exécutant en séquence. Dans la programmation synchronisée, le programme est exécuté ligne par ligne, une ligne à la fois. Chaque fois qu'une fonction est appelée, l'exécution du programme attend le retour de cette fonction avant de passer à la ligne de code suivante.

L' exécution asynchrone (ou asynchrone) fait référence à une exécution qui ne s'exécute pas dans l'ordre dans lequel elle apparaît dans le code. Dans la programmation asynchrone, le programme n'attend pas la fin de la tâche et peut passer à la tâche suivante.

Dans l'exemple suivant, l'opération de synchronisation provoque le déclenchement des alertes en séquence. Dans l'opération asynchrone, alors que alert (2) semble s'exécuter en second, ce n'est pas le cas.

// Synchronous: 1,2,3 alert(1); alert(2); alert(3); // Asynchronous: 1,3,2 alert(1); setTimeout(() => alert(2), 0); alert(3);

Une opération asynchrone est souvent liée aux E / S, bien qu'elle setTimeoutsoit un exemple de quelque chose qui n'est pas E / S mais toujours asynchrone. D'une manière générale, tout ce qui est lié au calcul est sync et tout ce qui est lié à l'entrée / sortie / synchronisation est asynchrone. La raison pour laquelle les opérations d'E / S doivent être effectuées de manière asynchrone est qu'elles sont très lentes et bloqueraient l'exécution du code dans le cas contraire.

Blocage vs non blocage

Le blocage fait référence aux opérations qui bloquent la poursuite de l'exécution jusqu'à ce que l'opération se termine alors que le non-blocage fait référence au code qui ne bloque pas l'exécution. Ou comme le dit la documentation de Node.js, le blocage se produit lorsque l'exécution de JavaScript supplémentaire dans le processus Node.js doit attendre la fin d'une opération non JavaScript.

Les méthodes de blocage s'exécutent de manière synchrone tandis que les méthodes non bloquantes s'exécutent de manière asynchrone.

// Blocking const fs = require('fs'); const data = fs.readFileSync('/file.md'); // blocks here until file is read console.log(data); moreWork(); // will run after console.log // Non-blocking const fs = require('fs'); fs.readFile('/file.md', (err, data) => { if (err) throw err; console.log(data); }); moreWork(); // will run before console.log

Dans le premier exemple ci-dessus, console.logsera appelé avant moreWork(). Dans le deuxième exemple, il fs.readFile()est non bloquant, donc l'exécution de JavaScript peut continuer et moreWork()sera appelée en premier.

Dans Node, le non-blocage fait principalement référence aux opérations d'E / S, et JavaScript qui présente des performances médiocres en raison de son utilisation intensive du processeur plutôt que d'attendre une opération non JavaScript, telle que les E / S, n'est généralement pas appelé blocage.

Toutes les méthodes d'E / S de la bibliothèque standard Node.js fournissent des versions asynchrones, qui ne sont pas bloquantes, et acceptent les fonctions de rappel. Certaines méthodes ont également des équivalents de blocage, dont les noms se terminent par Sync.

Les opérations d'E / S non bloquantes permettent à un seul processus de traiter plusieurs demandes en même temps. Au lieu de bloquer le processus et d'attendre la fin des opérations d'E / S, les opérations d'E / S sont déléguées au système, de sorte que le processus puisse exécuter le morceau de code suivant. Les opérations d'E / S non bloquantes fournissent une fonction de rappel qui est appelée lorsque l'opération est terminée.

Rappels

Un rappel est une fonction passée comme argument dans une autre fonction, qui peut ensuite être invoquée (rappelée) à l'intérieur de la fonction externe pour effectuer une sorte d'action à un moment opportun. L'appel peut être immédiat (rappel de synchronisation) ou il peut se produire ultérieurement (rappel asynchrone).

// Sync callback function greetings(callback) { callback(); } greetings(() => { console.log('Hi'); }); moreWork(); // will run after console.log // Async callback const fs = require('fs'); fs.readFile('/file.md', function callback(err, data) { // fs.readFile is an async method provided by Node if (err) throw err; console.log(data); }); moreWork(); // will run before console.log 

Dans le premier exemple, la fonction de rappel est appelée immédiatement dans la fonction d'accueil externe et se connecte à la console avant de moreWork()continuer.

Dans le deuxième exemple, fs.readFile (une méthode asynchrone fournie par Node) lit le fichier et, une fois terminé, appelle la fonction de rappel avec une erreur ou le contenu du fichier. En attendant, le programme peut continuer l'exécution du code.

Un rappel asynchrone peut être appelé lorsqu'un événement se produit ou lorsqu'une tâche se termine. Il empêche le blocage en permettant à d'autres codes d'être exécutés entre-temps.

Au lieu de lire le code de haut en bas de manière procédurale, les programmes asynchrones peuvent exécuter différentes fonctions à des moments différents en fonction de l'ordre et de la vitesse des fonctions précédentes telles que les requêtes http ou les lectures de système de fichiers. Ils sont utilisés lorsque vous ne savez pas quand une opération asynchrone se terminera.

Vous devez éviter «l' enfer des rappels », une situation où les rappels sont imbriqués dans d'autres rappels à plusieurs niveaux de profondeur, ce qui rend le code difficile à comprendre, à maintenir et à déboguer.

Événements et programmation événementielle

Les événements sont des actions générées par l'utilisateur ou le système, comme un clic, un téléchargement de fichier terminé ou une erreur matérielle ou logicielle.

La programmation événementielle est un paradigme de programmation dans lequel le flux du programme est déterminé par des événements. Un programme événementiel effectue des actions en réponse à des événements. Lorsqu'un événement se produit, il déclenche une fonction de rappel.

Maintenant, essayons de comprendre Node et voyons comment tout cela s'y rapporte.

Node.js: qu'est-ce que c'est, pourquoi a-t-il été créé et comment ça marche?

En termes simples, Node.js est une plate-forme qui exécute des programmes JavaScript côté serveur capables de communiquer avec des sources d'E / S telles que des réseaux et des systèmes de fichiers.

Lorsque Ryan Dahl a créé Node en 2009, il a fait valoir que les E / S n'étaient pas gérées correctement, bloquant l'ensemble du processus en raison de la programmation synchrone.

Traditional web-serving techniques use the thread model, meaning one thread for each request. Since in an I/O operation the request spends most of the time waiting for it to complete, intensive I/O scenarios entail a large amount of unused resources (such as memory) linked to these threads. Therefore the “one thread per request” model for a server doesn’t scale well.

Dahl argued that software should be able to multi-task and proposed eliminating the time spent waiting for I/O results to come back. Instead of the thread model, he said the right way to handle several concurrent connections was to have a single-thread, an event loop and non-blocking I/Os. For example, when you make a query to a database, instead of waiting for the response you give it a callback so your execution can run through that statement and continue doing other things. When the results come back you can execute the callback.

The event loop is what allows Node.js to perform non-blocking I/O operations despite the fact that JavaScript is single-threaded. The loop, which runs on the same thread as the JavaScript code, grabs a task from the code and executes it. If the task is async or an I/O operation the loop offloads it to the system kernel, like in the case for new connections to the server, or to a thread pool, like file system related operations. The loop then grabs the next task and executes it.

Since most modern kernels are multi-threaded, they can handle multiple operations executing in the background. When one of these operations completes (this is an event), the kernel tells Node.js so that the appropriate callback (the one that depended on the operation completing) may be added to the poll queue to eventually be executed.

Node keeps track of unfinished async operations, and the event loop keeps looping to check if they are finished until all of them are.

To accommodate the single-threaded event loop, Node.js uses the libuv library, which, in turn, uses a fixed-sized thread pool that handles the execution of some of the non-blocking asynchronous I/O operations in parallel. The main thread call functions post tasks to the shared task queue, which threads in the thread pool pull and execute.

Inherently non-blocking system functions such as networking translate to kernel-side non-blocking sockets, while inherently blocking system functions such as file I/O run in a blocking way on their own threads. When a thread in the thread pool completes a task, it informs the main thread of this, which in turn, wakes up and executes the registered callback.

The above image is taken from Philip Roberts’ presentation at JSConf EU: What the heck is the event loop anyway? I recommend watching the full video to get a high level idea about how the event loop works.

The diagram explains how the event loop works with the browser but it looks basically identical for Node. Instead of web APIs we would have Node APIs.

According to the presentation, the call stack (aka execution stack or “the stack”) is a data structure which records where in the program we are. If we step into a function, we put something onto the stack. If we return from a function, we pop it off the top of the stack.

This is how the code in the diagram is processed when we run it:

  1. Push main() onto the stack (the file itself)
  2. Push console.log(‘Hi’); onto the stack, which executes immediately logging “Hi” to the console and gets popped off the stack
  3. Push setTimeout(cb, 5000) onto the stack. setTimeout is an API provided by the browser (on the backend it would be a Node API). When setTimeout is called with the callback function and delay arguments, the browser kicks off a timer with the delay time
  4. The setTimeout call is completed and gets popped off the stack
  5. Push console.log(‘JSConfEU’); onto the stack, which executes immediately logging “JSConfEU” to the console and gets popped off the stack
  6. main() gets popped off the stack
  7. After 5000 milliseconds the API timer completes and the callback gets moved to the task queue
  8. The event loop checks if the stack is empty because JavaScript, being single-threaded, can only do one thing at a time (setTimeout is not a guaranteed but a minimum time to execution). If the stack is empty it takes the first thing on the queue and pushes it onto the stack. Therefore the loop pushes the callback onto the stack
  9. The callback gets executed, logs “there” to the console and gets popped off the stack. And we are done

If you want to go even deeper into the details on how Node.js, libuv, the event loop and the thread pool work, I suggest checking the resources on the reference section at the end, in particular this, this and this along with the Node docs.

Node.js: why and where to use it?

Since almost no function in Node directly performs I/O, the process never blocks (I/O operations are offloaded and executed asynchronously in the system), making it a good choice to develop highly scalable systems.

Due to its event-driven, single-threaded event loop and asynchronous non-blocking I/O model, Node.js performs best on intense I/O applications requiring speed and scalability with lots of concurrent connections, like video & audio streaming, real-time apps, live chats, gaming apps, collaboration tools, or stock exchange software.

Node.js may not be the right choice for CPU intensive operations. Instead the traditional thread model may perform better.

npm

npm is the default package manager for Node.js and it gets installed into the system when Node.js is installed. It can manage packages that are local dependencies of a particular project, as well as globally-installed JavaScript tools.

www.npmjs.com hosts thousands of free libraries to download and use in your program to make development faster and more efficient. However, since anybody can create libraries and there’s no vetting process for submission, you have to be careful about low quality, insecure, or malicious ones. npm relies on user reports to take down packages if they violate policies, and to help you decide, it includes statistics like number of downloads and number of depending packages.

How to run code in Node.js

Start by installing Node on your computer if you don’t have it already. The easiest way is to visit nodejs.org and click to download it. Unless you want or need to have access to the latest features, download the LTS (Long Term Support) version for you operating system.

You run a Node application from your computer’s terminal. For example make a file “app.js” and add console.log(‘Hi’); to it. On your terminal change the directory to the folder where this file belongs to and run node app.js. It will log “Hi” to the console. ?

References

Here are some of the interesting resources I reviewed during the writing of the article.

Node.js presentations by its author:

  • Original Node.js presentation by Ryan Dahl at JSConf 2009
  • 10 Things I Regret About Node.js by Ryan Dahl at JSConf EU 2018

Node, the event loop and the libuv library presentations:

  • What the heck is the event loop anyway? by Philip Roberts at JSConf EU
  • Node.js Explained by Jeff Kunkle
  • In The Loop by Jake Archibald at JSConf Asia 2018
  • Everything You Need to Know About Node.js Event Loop by Bert Belder
  • A deep dive into libuv by Saul Ibarra Coretge at NodeConf EU 2016

Node documents:

  • About Node.js
  • The Node.js Event Loop, Timers, and process.nextTick()
  • Overview of Blocking vs Non-Blocking

Additional resources:

  • Art of Node by Max Ogden
  • Callback hell by Max Ogden
  • What is non-blocking or asynchronous I/O in Node.js? on Stack Overflow
  • Event driven programming on Wikipedia
  • Node.js on Wikipedia
  • Thread on Wikipedia
  • libuv

Thanks for reading.