
Gestion des erreurs et des événements de cycle de vie des streams
Maîtrisez la gestion des erreurs (`error`) et les evenements essentiels (`end`, `finish`, `close`, `drain`) du cycle de vie des streams Node.js pour des applications fiables.
L'importance de la gestion des evenements et des erreurs
Les streams, par nature, sont des entités dynamiques qui opèrent de manière asynchrone. Ils interagissent avec des ressources externes (fichiers, réseau, etc.) qui peuvent rencontrer des problèmes ou simplement atteindre la fin de leur opération. Pour construire des applications Node.js robustes et fiables, il est absolument crucial de comprendre et de gérer correctement les différents événements qui jalonnent le cycle de vie d'un stream, ainsi que les erreurs potentielles.
Ignorer les erreurs sur un stream peut entraîner des comportements inattendus, des fuites de ressources (comme des descripteurs de fichiers non fermés) et, dans le pire des cas, le crash de votre application. De même, ne pas réagir correctement aux événements de fin ou de fermeture peut laisser des opérations incomplètes ou bloquer indéfiniment des processus.
Ce chapitre se concentre sur l'identification et la gestion des événements les plus importants du cycle de vie des streams (Readable, Writable, Duplex, Transform) et met un accent particulier sur la capture et le traitement systématique des erreurs.
L'evenement 'error' : le filet de securite indispensable
L'événement le plus critique à gérer sur tous les types de streams est l'événement `error`. Il est émis lorsqu'une erreur se produit pendant l'opération du stream (lecture, écriture, transformation). Cela peut être dû à une multitude de raisons : fichier non trouvé, permissions insuffisantes, problème réseau, erreur de parsing des données, etc.
La règle d'or est : toujours attacher un écouteur pour l'événement `error` sur chaque stream que vous manipulez. Si un événement `error` est émis et qu'aucun écouteur n'est enregistré, Node.js considérera cela comme une exception non interceptée, ce qui entraînera par défaut l'arrêt brutal de votre processus.
Voici comment écouter l'événement `error` :
const fs = require('fs');
const readableStream = fs.createReadStream('fichier_inexistant.txt');
const writableStream = fs.createWriteStream('/chemin/protege/output.txt');
readableStream.on('error', (err) => {
console.error('Erreur sur le stream Readable:', err.message);
// Ici, vous pourriez vouloir nettoyer des ressources, logger l'erreur, etc.
});
writableStream.on('error', (err) => {
console.error('Erreur sur le stream Writable:', err.message);
// Gérer l'erreur d'écriture
});
// Meme si on utilise pipe, il est bon de gérer les erreurs individuellement
readableStream.pipe(writableStream);
Attacher un gestionnaire d'erreur empêche le crash et vous permet de réagir de manière appropriée, par exemple en informant l'utilisateur, en tentant une autre opération ou en arrêtant proprement une partie de l'application.
Evenements cles du cycle de vie des streams Readable
Outre `error`, les streams Readable possèdent plusieurs événements importants :
- `data` : Emis lorsqu'un chunk de données est disponible pour la lecture (principalement en mode flowing, ou après un appel à `read()` en mode paused).
- `readable` : Emis lorsqu'il y a des données prêtes à être lues via `read()` (mode paused). C'est le signal pour commencer ou continuer la lecture.
- `end` : Emis une seule fois, lorsque la source a envoyé toutes ses données. Après cet événement, `data` ne sera plus jamais émis. C'est le signal que la lecture est terminée du point de vue de la source.
- `close` : Emis lorsque le stream et sa ressource sous-jacente (ex: descripteur de fichier, socket) ont été fermés. Cet événement n'est pas toujours émis après `end` (par exemple, un socket peut rester ouvert après la fin de la lecture). Il garantit que la ressource est libérée.
Comprendre la différence entre `end` et `close` est important : `end` signifie "plus de données à lire", tandis que `close` signifie "la ressource est fermée".
Evenements cles du cycle de vie des streams Writable
Les streams Writable ont également leur propre jeu d'événements essentiels :
- `drain` : Emis lorsque le buffer interne du stream Writable, qui était précédemment plein (suite à un appel à `write()` retournant `false`), est de nouveau vide ou suffisamment vidé pour accepter de nouvelles données. C'est le signal pour reprendre l'écriture si elle avait été mise en pause à cause de la contre-pression.
- `finish` : Emis une seule fois, après que la méthode `end()` a été appelée et que toutes les données en attente dans le buffer interne ont été écrites (flushées) avec succès vers la destination sous-jacente (fichier, réseau...). C'est le signal que l'écriture est terminée du point de vue du stream.
- `pipe` : Emis lorsqu'un stream Readable est connecté à ce stream Writable via la méthode `pipe()`. L'objet Readable source est passé en argument.
- `unpipe` : Emis lorsqu'un stream Readable est déconnecté de ce stream Writable via la méthode `unpipe()`.
- `close` : Comme pour les Readable, émis lorsque le stream et sa ressource sous-jacente sont fermés. Il est généralement émis après `finish`, mais peut aussi l'être en cas d'erreur prématurée.
Ici aussi, la distinction entre `finish` ("toutes les données ont été envoyées par le stream") et `close` ("la ressource est fermée") est cruciale.
Gestion des erreurs et de la fin dans les pipelines (`pipe`)
Lorsque vous utilisez `readable.pipe(writable)`, Node.js gère automatiquement certains aspects. Par défaut, si une erreur se produit sur le `readable`, le `writable` est automatiquement fermé (détruit). De même, lorsque le `readable` émet `end`, `pipe()` appelle `writable.end()`.
Cependant, le comportement par défaut ne couvre pas tous les cas, notamment les erreurs sur le `writable`. Si une erreur survient sur le `writable` (ex: disque plein), le `readable` ne sera pas automatiquement détruit par défaut, ce qui peut laisser des ressources ouvertes. C'est pourquoi il est recommandé d'ajouter des gestionnaires d'erreurs sur chaque stream d'un pipeline.
Pour une gestion plus robuste et simplifiée des pipelines complexes (plusieurs streams enchaînés) et de leurs erreurs, Node.js propose l'utilitaire `stream.pipeline` ou `stream.promises.pipeline` (version basée sur les promesses) qui gère correctement la destruction de tous les streams du pipeline en cas d'erreur sur l'un d'eux et fournit un callback ou une promesse unique pour la fin ou l'erreur du pipeline entier.
const { pipeline } = require('stream');
const fs = require('fs');
const zlib = require('zlib');
pipeline(
fs.createReadStream('archive.gz'),
zlib.createGunzip(),
fs.createWriteStream('fichier_decompresse.txt'),
(err) => {
if (err) {
console.error('Erreur dans le pipeline :', err);
} else {
console.log('Pipeline terminé avec succès.');
}
}
);L'utilisation de `pipeline` est la méthode moderne et recommandée pour assembler des streams de manière fiable.