web-dev-qa-db-fra.com

Gestion des erreurs avec les flux node.js

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:

  • quand une erreur se produit, qu'advient-il de l'événement 'end'? Est-ce qu'il ne se fait jamais virer? Est-il parfois renvoyé? Cela dépend-il de la transformation/du flux? Quelles sont les normes ici?
  • existe-t-il des mécanismes permettant de propager des erreurs dans les tuyaux?
  • les domaines résolvent-ils ce problème efficacement? Les exemples seraient Nice.
  • les erreurs résultant d'événements 'error' ont-elles des traces de pile? Parfois? Jamais? y a-t-il un moyen d'en obtenir un?
125
B T

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. 


Where you might put a transform stream


Pour savoir comment créer un flux de transformation, voyez ici et ici . Tout ce que tu dois faire est :

  1. inclure le module de flux
  2. instancier (ou hériter de) la classe Transform 
  3. implémenter une méthode _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);
 });
  • l'exemple de code ci-dessus provient de this post

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.

182
mshell_lauren

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.

21
Bent Cardan

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, ... ]);
6
Gleba

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.

4
shusson

.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

3
Vikas Gautam

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);
   });
0
Derek