> ## Content Index
> Fetch the complete content index at: https://www.sfeir.dev/llms.txt
> Use this file to discover other available public pages before exploring further.

# Les événements Node.js par l'exemple
- URL: https://www.sfeir.dev/back/backend-node-js-events-mailbox-tips/
- Published: 2023-05-03T15:52:51.000Z
- Updated: 2023-11-02T11:41:10.000Z
- Description: Les événements régissent une grande partie du noyau #Nodejs. Partons d'une boîte aux lettres et (re)découvrons leur fonctionnement.
- Author: Alexandre Moevi
- Tags: Back, Node.js, Tips

En Node.js, quand on veut qu'une action se réalise à la suite d'une autre, on peut opter pour l'imbrication de callbacks ou l'enchaînement de promesses. Mais ces deux techniques lient étroitement l'action initiale et l'action consécutive, rendant plus compliquée toute modification ultérieure de l'action consécutive. 

Les émetteurs d'événements proposent une autre manière de structurer cette relation : le modèle de publication et souscription. Dans ce modèle, un éditeur (l'émetteur d'événements) transmet un message (un événement), et un souscripteur reçoit l'événement et effectue une action.

L'avantage de ce modèle est que l'éditeur n'a pas besoin de connaître les souscripteurs. Un éditeur émet un message, et c'est à chaque souscripteur de réagir à ce message de la manière qui lui convient le mieux. Si l'on souhaite modifier le comportement de notre application, il est possible de changer la façon dont les souscripteurs réagissent aux événements, sans avoir à modifier l'éditeur.

Nous allons illustrer cette notion d'événementiel avec un peu de courrier ✉️

## Créer un premier événement

Le module `events` va nous permettre de créer et de traiter les événements. Dans ce module se trouve la classe `EventEmitter`. Des objets de cette classe vont pouvoir émettre des événements avec la méthode `emit()`.

```javascript
const { EventEmitter } = require("events");

const ourEmitter = new EventEmitter();
ourEmitter.emit('hello');
```

Nous allons créer une classe `Mailbox`, qui hérite de l'`EventEmitter`, pour représenter notre boîte aux lettres. À cette boîte aux lettres, nous ajoutons une taille maximale. Une fois cette limite atteinte, nous considérerons qu'elle est pleine et qu'elle ne peut plus recevoir de courrier supplémentaire.

```javascript
const { EventEmitter } = require("events");

class Mailbox extends EventEmitter {
    constructor(maxSize) {
        super();
        this.maxSize = maxSize;
        this.box = [];
    }
}
```

mailbox.js

Ajoutons dans la classe une méthode `receiveMail()` qui va envoyer un événement quand un nouveau message arrive dans la boîte. Une fois la boîte pleine, nous arrêtons d'insérer de nouveaux messages mais nous écrivons un log.

```javascript
receiveMail(email) {
    if (this.box.length < this.maxSize) {
        this.box.push(email);
        this.emit("mail");
        return;
    }
    console.error("Mailbox is full");
}
```

mailbox.js

## Traiter les événements

Notre boîte aux lettres est capable d'émettre des événements mais malheureusement, personne ne les écoute actuellement. Pour y remédier, ajoutons un souscripteur avec la méthode `on()`.

```javascript
const mailbox = new Mailbox(3);
mailbox.on("mail", () => console.log("You've got mail!"));
mailbox.receiveMail("hello");
```

mailbox.js

La fonction `on` prend en paramètre le nom de l'événement qu'on souhaite écouter et une fonction callback qui va traiter l'événement. Dans notre cas, on souhaite écouter l'évènement `mail` émis par notre boîte aux lettres et lorsqu'un nouveau courrier arrive, on log un `"You've got mail!"`.

```
$ node mailbox.js
You've got mail!
```

> Il existe également la fonction `addListener()` pour ajouter un souscripteur. Les deux fonctions ont la même utilité, puisque `addListener()` est en fait un alias de la méthode `on()`.

Comme notre boîte ne peut contenir que 3 messages, si l'on essaye d'envoyer plus de 3 messages, la boîte arrêtera d'envoyer des événements `mail` et affichera dans le terminal un message de saturation.

```javascript
const mailbox = new Mailbox(3);
mailbox.on("mail", () => console.log("You've got mail!"));
mailbox.receiveMail("hello");	// Affiche "You've got mail!"
mailbox.receiveMail("this");	// Affiche "You've got mail!"
mailbox.receiveMail("is");		// Affiche "You've got mail!"
mailbox.receiveMail("me");		// Affiche "Mailbox is full"
mailbox.receiveMail("!");		// Affiche "Mailbox is full"
```

Un autre souscripteur qui n'a pas l'envie d'être sollicité en permanence veut écouter la boîte aux lettres qu'une seule fois. Ainsi la méthode `once()` permet d'écouter l'événement de façon unique avant de se désabonner automatiquement.

```javascript
mailbox.on("mail", () => console.log("You've got mail!"));
mailbox.once("mail", () => console.log("One mail is all it takes..."));
mailbox.receiveMail("first mail");
mailbox.receiveMail("second mail");
mailbox.receiveMail("last mail");

// You've got mail!
// One mail is all it takes...
// You've got mail!
// You've got mail!
```

Comme vous l'avez compris, il y a bien deux souscripteurs pour le même événement. Mais il est tout à fait possible de rajouter plusieurs souscripteurs qui écoutent le même événement, et chaque souscripteur va lancer son callback quand l'événement sera émis.

## Enrichir les messages

Pour le moment, notre boîte aux lettres envoie des événements vides, c'est-à-dire sans donnée supplémentaire. La méthode `emit()` de la classe `EventEmitter` accepte des paramètres supplémentaires, qui sont des éléments qui vont accompagner l'événement. Nous allons modifier la classe `Mailbox` afin d'émettre des événements enrichis.

```javascript
const { EventEmitter } = require("events");

class Mailbox extends EventEmitter {
    constructor(maxSize) {
        super();
        this.maxSize = maxSize;
        this.box = [];
    }

    receiveMail(email) {
        if (this.box.length < this.maxSize) {
            this.box.push(email);
            this.emit("mail", email, Date.now());
            return;
        }
        console.error("Mailbox is full");
    }
}

const mailbox = new Mailbox(3);
mailbox.on("mail", (email, date) => console.log(`Mail received at ${date}! It says: ${email}`));
mailbox.receiveMail("hello");
mailbox.receiveMail("this");
mailbox.receiveMail("is");
mailbox.receiveMail("me");
mailbox.receiveMail("!");
```

mailbox.js

L'événement `mail` est enrichi avec le texte du mail et la date de réception. Quand on lance le programme, on voit que le callback récupère les éléments envoyés par notre boîte aux lettres. 

```
$ node mailbox.js
Mail received at 1683031214652! It says: hello
Mail received at 1683031214655! It says: this
Mail received at 1683031214655! It says: is
Mailbox is full
Mailbox is full
```

## Gestions des erreurs

Modifions une nouvelle fois le fichier. Au lieu d'écrire dans la console, nous allons émettre un événement `error` qui contient une erreur. 

```javascript
receiveMail(email) {
    if (this.box.length < this.maxSize) {
        this.box.push(email);
        this.emit("mail", email, Date.now());
        return;
    }
    this.emit("error", new Error("Mailbox is full"));
}
```

Personne n'écoute cet événement `error` pour le moment mais exécutons quand même le fichier pour voir ce qu'il se passe.

```
$ node mailbox.js
Mail received at 1683031792230! It says: hello
Mail received at 1683031792233! It says: this
Mail received at 1683031792233! It says: is
node:events:491
      throw er; // Unhandled 'error' event
      ^

Error: Mailbox is full
    at Mailbox.receiveMail (mails.js:16:28)
    at Object.<anonymous> (mails.js:25:9)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Module.load (node:internal/modules/cjs/loader:1117:32)
    at Module._load (node:internal/modules/cjs/loader:958:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:23:47
Emitted 'error' event on Mailbox instance at:
    at Mailbox.receiveMail (mails.js:16:14)
    at Object.<anonymous> (mails.js:25:9)
    [... lines matching original stack trace ...]
    at node:internal/main/run_main_module:23:47
```

Oups, la boîte aux lettres a fait exploser notre programme. Dans le message, on voit bien que l'événement `error` a été émis par notre boîte aux lettres (`Emitted 'error' event on Mailbox instance`) mais il n'a pas été géré (`Unhandled 'error' event`). 

Si un `EventEmitter` émet un événement `error` et qu'aucun souscripteur n'est disponible pour l'écouter, alors l'erreur est levée, la stack trace est imprimée et le processus Node.js se termine.

Il faut donc rajouter un `souscripteur` qui va se charger des erreurs émises par notre boîte aux lettres.

```javascript
const { EventEmitter } = require("events");

class Mailbox extends EventEmitter {
    constructor(maxSize) {
        super();
        this.maxSize = maxSize;
        this.box = [];
    }

    receiveMail(email) {
        if (this.box.length < this.maxSize) {
            this.box.push(email);
            this.emit("mail", email, Date.now());
            return;
        }
        this.emit("error", new Error("Mailbox is full"));
    }
}

const mailbox = new Mailbox(3);
mailbox.on("mail", (mail, date) => console.log(`Mail received at ${date}! It says: ${mail}`));
mailbox.on("error", (error) => console.log(`Something went wrong with the mailbox: ${error}`));
mailbox.receiveMail("hello");
mailbox.receiveMail("this");
mailbox.receiveMail("is");
mailbox.receiveMail("me");
mailbox.receiveMail("!");
```

mailbox.js

```
$ node mailbox.js
Mail received at 1683033385024! It says: hello
Mail received at 1683033385027! It says: this
Mail received at 1683033385027! It says: is
Something went wrong with the mailbox: Error: Mailbox is full
Something went wrong with the mailbox: Error: Mailbox is full
```

## Les événements dans le noyau Node.js

Étant donnée sa nature de moteur Javascript asynchrone, Node.js s'appuie lui-même sur les événements dans son propre noyau.

```javascript
const { createReadStream } = require('fs');

const readFileStream = createReadStream(__filename, { highWaterMark: 100 });
let bytesRead = 0;

readFileStream.on("ready", () => {
    console.log("Ready to read file");
});
readFileStream.on("data", (chunk) => {
    bytesRead += chunk.length; 
    console.log(`${chunk.length} bytes read`);
});
readFileStream.on("end", () => {
    console.log(`Finished reading file, ${bytesRead} bytes read in total`);
});

process.on("exit", (code) => {
    console.log(`Exiting Node, status code = ${code}`);
});
```

my-events.js

Dans ce fichier, on utilise le module `fs` ainsi que la variable globale `process` qui nous sont fournis clé en main par Node. La fonction `createReadStream` du module `fs` est importée et nous renvoie un flux de données lisant le fichier courant (`__filename`).

Les variables `readFileStream` et `process` écoutent des événements prédéfinis dans le cœur de Node.js, il n'y a pas de `emit(data)` que nous avons codé dans ce fichier. Comme avec notre boîte aux lettres, on écoute des événements, et on les traite. 

Le flux nous signale quand il est prêt à être utilisé avec un événement `ready`. Quand il obtient de la donnée, il envoie un événement `data` avec la donnée. Enfin, quand il n'y a plus de donnée à traiter, le flux émet un événement `end`.

Quant au `process`, il envoie un événement `exit` quand Node est sur le point de s'arrêter. On décide de l'écouter, et on affiche le code de sortie du processus (0 si tout s'est bien passé, un nombre non nul en cas d'échec).

Si on exécute ce code, on voit les différents événements émis nativement au fil du temps.

```
$ node my-events.js
Ready to read file
100 bytes read
100 bytes read
100 bytes read
100 bytes read
100 bytes read
48 bytes read
Finished reading file, 548 bytes read in total
Exiting Node, status code = 0
```

## Pour aller plus loin

**Approfondissez vos connaissances sur le module `events` avec la [documentation officielle de Node.js](https://nodejs.org/api/events.html?ref=sfeir.dev).**

Ensuite, pourquoi ne pas explorer d'autres mécanismes d'événements, cette fois-ci côté navigateur ? Découvrez comment le Local Storage utilise les événements en JavaScript pour créer des interactions dynamiques.

[Découvrez la puissance des événements du Local Storage en JavaScriptRévolutionnez vos applications web en exploitant pleinement les événements du Local Storage![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/size/w256h256/2023/03/AXO_GOOD-2.png)sfeir.devLesage Yann![](https://storage.ghost.io/c/0b/31/0b31478e-a66a-46f1-8c34-d44388e21192/content/images/2023/06/tianyi-ma-WiONHd_zYI4-unsplash.jpg)](https://www.sfeir.dev/front/event-sur-le-local-storage-js)