Quelle est la bonne façon de gérer les erreurs avec les flux? Je sais déjà il y a un événement « d'erreur » que vous pouvez écouter, mais je veux savoir quelques détails sur les situations compliquées de façon arbitraire.
Pour commencer, que faites-vous lorsque vous souhaitez créer une chaîne de tubes simple:
input.pipe(transformA).pipe(transformB).pipe(transformC)...
Et comment créer correctement l'une de ces transformations pour que les erreurs soient traitées correctement?
Plus de questions connexes:
transformer
Les flux de transformation sont à la fois lisibles et inscriptibles, et constituent donc de très bons flux "intermédiaires". Pour cette raison, ils sont parfois appelés flux through
. Ils sont semblables à un Steam duplex de cette manière, à la différence qu’ils fournissent une interface agréable pour manipuler les données plutôt que de les envoyer. Le but d'un flux de transformation est de manipuler les données lorsqu'elles sont acheminées dans le flux. Vous voudrez peut-être faire des appels asynchrones par exemple, ou dériver quelques champs, remapper certaines choses, etc.
Pour savoir comment créer un flux de transformation, voyez ici et ici . Tout ce que tu dois faire est :
_transform
qui prend un (chunk, encoding, callback)
. Le morceau est vos données. La plupart du temps, vous n'aurez pas à vous soucier de l'encodage si vous travaillez dans objectMode = true
. Le rappel est appelé lorsque vous avez terminé le traitement du bloc. Ce morceau est ensuite poussé vers le flux suivant.
Si vous voulez un module d’aide Nice qui vous permettra de faire très facilement le flux, je suggère à travers2 .
Pour la gestion des erreurs, continuez à lire.
pipe
Dans une chaîne de distribution, la gestion des erreurs est en effet non triviale. Selon ce thread .pipe () n’est pas conçu pour transmettre les erreurs. Donc, quelque chose comme ...
var a = createStream();
a.pipe(b).pipe(c).on('error', function(e){handleError(e)});
... n'écouterait que les erreurs sur le flux c
. Si un événement d'erreur était émis sur a
, il ne serait pas transmis et, en fait, serait lancé. Pour le faire correctement:
var a = createStream();
a.on('error', function(e){handleError(e)})
.pipe(b)
.on('error', function(e){handleError(e)})
.pipe(c)
.on('error', function(e){handleError(e)});
Maintenant, bien que la deuxième voie soit plus détaillée, vous pouvez au moins conserver le contexte dans lequel vos erreurs se produisent. C'est généralement une bonne chose.
Une bibliothèque que je trouve utile cependant, si vous avez un cas dans lequel vous voulez uniquement capturer les erreurs à la destination et vous ne vous souciez pas trop de savoir où cela s’est passé est event-stream .
fin
Lorsqu'un événement d'erreur est déclenché, l'événement de fin ne sera pas déclenché (explicitement). L'émission d'un événement d'erreur mettra fin au flux.
domaines
D'après mon expérience, les domaines fonctionnent vraiment bien la plupart du temps. Si vous avez un événement d'erreur non géré (c'est-à-dire émettre une erreur sur un flux sans écouteur), le serveur peut se bloquer. Maintenant, comme le souligne l'article ci-dessus, vous pouvez envelopper le flux dans un domaine qui devrait correctement contenir toutes les erreurs.
var d = domain.create();
d.on('error', handleAllErrors);
d.run(function() {
fs.createReadStream(tarball)
.pipe(gzip.Gunzip())
.pipe(tar.Extract({ path: targetPath }))
.on('close', cb);
});
La beauté des domaines réside dans le fait qu’ils préservent les traces de la pile. Bien que event-stream fasse également un bon travail à cet égard.
Pour plus de lecture, consultez le stream-handbook . Assez en profondeur, mais très utile et donne d'excellents liens vers de nombreux modules utiles.
les domaines sont obsolètes. vous n'en avez pas besoin.
pour cette question, les distinctions entre transformer et écrire ne sont pas si importantes.
la réponse de mshell_lauren est excellente, mais vous pouvez également écouter explicitement l'événement d'erreur sur chaque flux susceptible de générer une erreur. et réutilisez la fonction de gestionnaire si vous préférez.
var a = createReadableStream()
var b = anotherTypeOfStream()
var c = createWriteStream()
a.on('error', handler)
b.on('error', handler)
c.on('error', handler)
a.pipe(b).pipe(c)
function handler (err) { console.log(err) }
cela empêche l’infâme exception non capturée si l’un de ces flux déclenche son erreur.
Les erreurs de la chaîne entière peuvent être propagées dans le flux le plus à droite en utilisant une simple fonction:
function safePipe (readable, transforms) {
while (transforms.length > 0) {
var new_readable = transforms.shift();
readable.on("error", function(e) { new_readable.emit("error", e); });
readable.pipe(new_readable);
readable = new_readable;
}
return readable;
}
qui peut être utilisé comme:
safePipe(readable, [ transform1, transform2, ... ]);
Si vous utilisez node> = v10.0.0, vous pouvez utiliser stream.pipeline et stream.finished .
Par exemple:
const { pipeline, finished } = require('stream');
pipeline(
input,
transformA,
transformB,
transformC,
(err) => {
if (err) {
console.error('Pipeline failed', err);
} else {
console.log('Pipeline succeeded');
}
});
finished(input, (err) => {
if (err) {
console.error('Stream failed', err);
} else {
console.log('Stream is done reading');
}
});
Voir ce github PR pour plus de discussion.
.on("error", handler)
ne s'occupe que des erreurs de flux, mais si vous utilisez des flux de transformation personnalisés, .on("error", handler)
n'intercepte pas les erreurs qui se produisent dans la fonction _transform
. Donc, on peut faire quelque chose comme ceci pour contrôler le flux de l'application: -
Le mot clé this
dans la fonction _transform
fait référence à Stream
elle-même, qui est une EventEmitter
. Vous pouvez donc utiliser try catch
comme ci-dessous pour capturer les erreurs et les transmettre plus tard aux gestionnaires d'événements personnalisés.
// CustomTransform.js
CustomTransformStream.prototype._transform = function (data, enc, done) {
var stream = this
try {
// Do your transform code
} catch (e) {
// Now based on the error type, with an if or switch statement
stream.emit("CTError1", e)
stream.emit("CTError2", e)
}
done()
}
// StreamImplementation.js
someReadStream
.pipe(CustomTransformStream)
.on("CTError1", function (e) { console.log(e) })
.on("CTError2", function (e) { /*Lets do something else*/ })
.pipe(someWriteStream)
De cette façon, vous pouvez garder votre logique et vos gestionnaires d’erreur séparés. En outre, vous pouvez choisir de ne gérer que certaines erreurs et d’ignorer d’autres.
METTRE À JOUR
Alternative: RXJS Observable
Utilisez le modèle Node.js en créant un mécanisme de flux de transformation et en appelant son rappel done
avec un argument afin de propager l'erreur:
var transformStream1 = new stream.Transform(/*{objectMode: true}*/);
transformStream1.prototype._transform = function (chunk, encoding, done) {
//var stream = this;
try {
// Do your transform code
/* ... */
} catch (error) {
// nodejs style for propagating an error
return done(error);
}
// Here, everything went well
done();
}
// Let's use the transform stream, assuming `someReadStream`
// and `someWriteStream` have been defined before
someReadStream
.pipe(transformStream1)
.on('error', function (error) {
console.error('Error in transformStream1:');
console.error(error);
process.exit(-1);
})
.pipe(someWriteStream)
.on('close', function () {
console.log('OK.');
process.exit();
})
.on('error', function (error) {
console.error(error);
process.exit(-1);
});