Error al manejar con secuencias de node.js

164

¿Cuál es la forma correcta de manejar los errores con las transmisiones? Ya sé que hay un evento de "error" en el que puedes escuchar, pero quiero saber más detalles sobre situaciones arbitrariamente complicadas.

Para empezar, ¿qué haces cuando quieres hacer una cadena de tubería simple?

input.pipe(transformA).pipe(transformB).pipe(transformC)...

¿Y cómo se crea correctamente una de esas transformaciones para que los errores se manejen correctamente?

Más preguntas relacionadas:

  • cuando ocurre un error, ¿qué sucede con el evento 'final'? ¿Nunca lo despiden? ¿A veces se despide? ¿Depende de la transformación / secuencia? ¿Cuáles son los estándares aquí?
  • ¿Existen mecanismos para propagar errores a través de las tuberías?
  • Por qué los dominios resuelven este problema de manera efectiva? Los ejemplos estarían bien.
  • ¿Los errores que salen de los eventos de 'error' tienen rastros de pila? ¿Algunas veces? ¿Nunca? ¿hay alguna manera de obtener uno de ellos?
BT
fuente
1
Esto no es trivial. Promiseframeworks lo hacen mucho más simple
slezica
27
Desafortunadamente, las promesas / futuros realmente no pueden ayudarte con las transmisiones ...
BT

Respuestas:

222

transformar

Las secuencias de transformación son legibles y se pueden escribir y, por lo tanto, son muy buenas secuencias 'intermedias'. Por esta razón, a veces se les conoce como throughsecuencias. Son similares a una transmisión dúplex de esta manera, excepto que proporcionan una interfaz agradable para manipular los datos en lugar de simplemente enviarlos. El propósito de una secuencia de transformación es manipular los datos a medida que se canalizan a través de la secuencia. Es posible que desee hacer algunas llamadas asíncronas, por ejemplo, o derivar un par de campos, reasignar algunas cosas, etc.


Donde podrías poner una secuencia de transformación


Para saber cómo crear una secuencia de transformación, vea aquí y aquí . Todo lo que tienes que hacer es :

  1. incluir el módulo de transmisión
  2. instanciar (o heredar de) la clase Transform
  3. implementar un _transformmétodo que tome a (chunk, encoding, callback).

El fragmento son tus datos. La mayoría de las veces no tendrá que preocuparse por la codificación si está trabajando en objectMode = true. Se llama a la devolución de llamada cuando haya terminado de procesar el fragmento. Este fragmento se pasa a la siguiente secuencia.

Si quieres un buen módulo de ayuda que le permitirá hacer a través de la corriente muy, muy fácilmente, sugiero through2 .

Para el manejo de errores, sigue leyendo.

tubo

En una cadena de tuberías, los errores de manejo no son triviales. De acuerdo con este hilo, .pipe () no está diseñado para reenviar errores. Entonces algo como ...

var a = createStream();
a.pipe(b).pipe(c).on('error', function(e){handleError(e)});

... solo escucharía los errores en la transmisión c. Si se emitiera un evento de error a, no se transmitiría y, de hecho, se lanzaría. Para hacer esto correctamente:

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

Ahora, aunque la segunda forma es más detallada, al menos puede mantener el contexto de dónde ocurren sus errores. Esto suele ser algo bueno.

Sin embargo, una biblioteca que encuentro útil si tiene un caso en el que solo desea capturar los errores en el destino y no le importa mucho dónde sucedió es el flujo de eventos .

final

Cuando se dispara un evento de error, el evento final no se disparará (explícitamente). La emisión de un evento de error finalizará la transmisión.

dominios

En mi experiencia, los dominios funcionan muy bien la mayor parte del tiempo. Si tiene un evento de error no controlado (es decir, emitiendo un error en una secuencia sin un oyente), el servidor puede bloquearse. Ahora, como señala el artículo anterior, puede envolver la transmisión en un dominio que debería detectar correctamente todos los errores.

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 belleza de los dominios es que preservarán los rastros de la pila. Aunque event-stream también hace un buen trabajo en esto.

Para leer más, consulte el manual de transmisión . Bastante en profundidad, pero súper útil y ofrece excelentes enlaces a muchos módulos útiles.

mshell_lauren
fuente
Esa es una gran información, ¡gracias! ¿Podría agregar un poco sobre por qué desea crear una secuencia de transformación y por qué se relaciona con mi pregunta?
BT
Claro, aunque supuse que estaba relacionado desde que lo preguntaste; )
mshell_lauren
1
Publique sobre esto por isaccs en Google Groups- nodejs: groups.google.com/d/msg/nodejs/lJYT9hZxFu0/L59CFbqWGyYJ (no grokbase)
jpillora
Esta respuesta está escrita perfectamente. Voy a investigar la sugerencia de dominio, parece ser el tipo de solución que estaba buscando.
Punto
12
Tenga en cuenta que no necesita envolver el .on('error')controlador en una función anónima, es decir a.on('error', function(e){handleError(e)}), simplemente puede sera.on('error', handleError)
timoxley
28

Si está usando el nodo> = v10.0.0 puede usar stream.pipeline y stream.finished .

Por ejemplo:

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');
  }
});

Vea este PR de Github para más discusión.

shusson
fuente
1
Sin finishedembargo, ¿por qué usarías cuando pipelineya tienes una devolución de llamada?
Marcos Pereira
44
Es posible que desee manejar los errores de manera diferente entre la canalización y las transmisiones individuales.
shusson
25

los dominios están en desuso. No los necesitas.

Para esta pregunta, las distinciones entre transformación o escritura no son tan importantes.

La respuesta de mshell_lauren es excelente, pero como alternativa, también puede escuchar explícitamente el evento de error en cada transmisión que cree que podría ser un error. y reutilice la función del controlador si lo prefiere.

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) }

hacerlo evita la infame excepción no descubierta en caso de que una de esas transmisiones active su evento de error

Cardan Doblado
fuente
3
jajaja, diviértete manejando 3 eventos de error diferentes y reza para que quien escribió las 3 bibliotecas de transmisión diferentes implemente el manejo de errores correctamente
Alexander Mills
44
@Alex Mills 1) ¿Cuál es el problema de manejar 3 eventos, y por qué son "diferentes", cuando su tipo es el mismo? errorUno también puede resolver el hecho de que cada evento es distinto; 2) ¿qué bibliotecas de streaming están escritas anteriormente, aparte de la funcionalidad nativa de Node.js? y 3) ¿por qué importa cómo manejan los eventos internamente, cuando esto obviamente le permite a alguien adjuntar controladores de errores adicionales además de lo que ya existe?
amn
10

Los errores de toda la cadena se pueden propagar a la secuencia más a la derecha utilizando una función simple:

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;
}

que se puede usar como:

safePipe(readable, [ transform1, transform2, ... ]);
Gleba
fuente
5

.on("error", handler)solo se ocupa de los errores de transmisión, pero si está utilizando transmisiones de transformación personalizadas, .on("error", handler)no detecte los errores que ocurren dentro de la _transformfunción. Entonces uno puede hacer algo como esto para controlar el flujo de la aplicación:

thisLa palabra clave en _transformfunción se refiere a Streamsí misma, que es un EventEmitter. Por lo tanto, puede usar el try catchsiguiente para detectar los errores y luego pasarlos a los controladores de eventos personalizados.

// 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 esta manera, puede mantener sus manejadores de lógica y error separados. Además, puede optar por manejar solo algunos errores e ignorar otros.

ACTUALIZACIÓN
Alternativa: RXJS Observable

Vikas Gautam
fuente
4

Use el paquete multipipe para combinar varias secuencias en una secuencia dúplex. Y manejar errores en un solo lugar.

const pipe = require('multipipe')

// pipe streams
const stream = pipe(streamA, streamB, streamC) 


// centralized error handling
stream.on('error', fn)
Sergey Savenko
fuente
1

Utilice el patrón Node.js creando una mecánica de flujo de Transformación y llamando su devolución de llamada donecon un argumento para propagar el error:

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);
   });
Derek
fuente
Hmm, ¿estás diciendo que si todos los procesadores de flujo se construyeran así, los errores se propagarían?
BT
-2

Intentar capturar no capturará los errores que ocurrieron en la transmisión porque se lanzan después de que el código de llamada ya ha salido. puedes consultar la documentación:

https://nodejs.org/dist/latest-v10.x/docs/api/errors.html

Mehran
fuente
Gracias, pero esto no responde a la pregunta en absoluto.
BT
Darme un documento de 40 páginas no es útil. ¿A qué crees que debería referirme en esa página gigante? Además, ¿has leído mi pregunta? Mi pregunta no es "¿intenta capturar el trabajo con transmisiones?" Ya soy consciente de que try-catch no funcionará con errores asincrónicos, por ejemplo, los de las tuberías de procesamiento de flujo.
BT