Erro ao manipular com fluxos node.js.

164

Qual é a maneira correta de lidar com erros com fluxos? Já sei que há um evento de 'erro' que você pode ouvir, mas quero saber mais detalhes sobre situações arbitrariamente complicadas.

Para iniciantes, o que você faz quando deseja fazer uma cadeia simples de tubos:

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

E como você cria adequadamente uma dessas transformações para que os erros sejam tratados corretamente?

Mais perguntas relacionadas:

  • quando ocorre um erro, o que acontece com o evento 'final'? Ele nunca é demitido? Às vezes é demitido? Depende da transformação / fluxo? Quais são os padrões aqui?
  • existem mecanismos para propagar erros através dos canos?
  • os domínios resolvem esse problema efetivamente? Exemplos seria bom.
  • erros que saem de eventos 'error' têm rastreamentos de pilha? As vezes? Nunca? existe uma maneira de obter um deles?
BT
fonte
1
Isto não é trivial. Promiseestruturas de torná-lo muito mais simples
slezica
27
Infelizmente promessas / futuros não pode realmente ajudá-lo com correntes ...
BT

Respostas:

222

transformar

Os fluxos de transformação são legíveis e graváveis ​​e, portanto, são realmente bons fluxos 'médios'. Por esse motivo, às vezes são chamados de throughfluxos. Eles são semelhantes a um fluxo duplex dessa maneira, exceto que eles fornecem uma interface agradável para manipular os dados em vez de apenas enviá-los. O objetivo de um fluxo de transformação é manipular os dados conforme eles são canalizados através do fluxo. Você pode fazer algumas chamadas assíncronas, por exemplo, ou derivar alguns campos, remapear algumas coisas, etc.


Onde você pode colocar um fluxo de transformação


Para saber como criar um fluxo de transformação, veja aqui e aqui . Tudo o que tem a fazer é :

  1. inclua o módulo de fluxo
  2. instanciar (ou herdar) a classe Transform
  3. implementar um _transformmétodo que leva a (chunk, encoding, callback).

O pedaço é seus dados. Na maioria das vezes, você não precisará se preocupar com a codificação se estiver trabalhando objectMode = true. O retorno de chamada é chamado quando você terminar de processar o bloco. Esse pedaço é então empurrado para o próximo fluxo.

Se você quer um bom módulo auxiliar que permita realizar o fluxo realmente muito facilmente, sugiro o through2 .

Para tratamento de erros, continue lendo.

tubo

Em uma cadeia de tubulação, o tratamento de erros é de fato não trivial. De acordo com este segmento, o .pipe () não foi criado para encaminhar erros. Então, algo como ...

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

... só ouviria erros no fluxo c. Se um evento de erro fosse emitido a, isso não seria transmitido e, de fato, seria lançado. Para fazer isso corretamente:

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

Agora, embora a segunda maneira seja mais detalhada, você pode pelo menos manter o contexto de onde seus erros ocorrem. Isso geralmente é uma coisa boa.

Uma biblioteca que eu acho útil, se você tem um caso em que deseja capturar apenas os erros no destino e não se importa muito com o local em que isso aconteceu, é o fluxo de eventos .

fim

Quando um evento de erro é acionado, o evento final não será acionado (explicitamente). A emissão de um evento de erro encerrará o fluxo.

domínios

Na minha experiência, os domínios funcionam muito bem na maioria das vezes. Se você tiver um evento de erro não tratado (ou seja, emitindo um erro em um fluxo sem um ouvinte), o servidor poderá travar. Agora, como o artigo acima indica, você pode agrupar o fluxo em um domínio que deve capturar corretamente todos os erros.

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

A beleza dos domínios é que eles preservarão os rastreamentos de pilha. Embora o fluxo de eventos faça um bom trabalho nisso também.

Para ler mais, confira o manual do stream . Bastante detalhado, mas super útil e oferece ótimos links para vários módulos úteis.

mshell_lauren
fonte
É realmente uma ótima informação, obrigado! Você poderia adicionar um pouco sobre por que você deseja criar um fluxo de transformação e por que ele está relacionado à minha pergunta?
BT
Claro - embora eu tenha percebido isso relacionado desde que você perguntou sobre isso; )
mshell_lauren 14/03
1
Post sobre este por isaccs no Google Grupos- NodeJS: groups.google.com/d/msg/nodejs/lJYT9hZxFu0/L59CFbqWGyYJ (não grokbase)
jpillora
Esta resposta está escrita perfeitamente. Vou investigar a sugestão de domínio - parece ser o tipo de solução que eu estava procurando.
Ponto
12
Note que você não precisa envolver o .on('error')manipulador em uma função anônima ou seja, a.on('error', function(e){handleError(e)})pode ser apenasa.on('error', handleError)
timoxley
28

Se você estiver usando o nó> = v10.0.0, poderá usar stream.pipeline e stream.finished .

Por exemplo:

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

Veja este github PR para mais discussão.

shusson
fonte
1
Por que você usaria finishedquando pipelinejá tem um retorno de chamada?
Marcos Pereira
4
Convém manipular os erros de maneira diferente entre o pipeline e os fluxos individuais.
shusson 02/07/19
25

domínios estão obsoletos. você não precisa deles.

para esta pergunta, as distinções entre transformação ou gravável não são tão importantes.

A resposta de mshell_lauren é ótima, mas, como alternativa, você também pode ouvir explicitamente o evento de erro em cada fluxo que você acha que pode ter erro. e reutilize a função de manipulador, se preferir.

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

isso evita a exceção infame e não capturada, se um desses fluxos disparar seu evento de erro

Bent Cardan
fonte
3
lol se divertir manipulação 3 eventos de erro diferentes e rezar para que quem escreveu a 3 diferentes de streaming libs implementado manipulação de erro corretamente
Alexander Mills
4
@Alex Mills 1) Qual é o problema de lidar com três eventos e por que eles são "diferentes", quando o seu tipo é o mesmo - error, pode-se então aceitar o fato de que cada evento é distinto; 2) quais bibliotecas de streaming são escritas acima, exceto a funcionalidade nativa do Node.js. e 3) por que importa como eles lidam com eventos internamente, quando isso obviamente permite a qualquer pessoa anexar manipuladores de erro adicionais sobre o que já existe?
01
10

Erros de toda a cadeia podem ser propagados para o fluxo mais à direita usando uma função simples:

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 pode ser usado como:

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

.on("error", handler)cuida apenas dos erros de fluxo, mas se você estiver usando fluxos personalizados do Transform, .on("error", handler)não identifique os erros que ocorrem dentro da _transformfunção. Portanto, é possível fazer algo assim para controlar o fluxo do aplicativo:

thisA palavra-chave na _transformfunção se refere a Streamsi mesma, que é um EventEmitter. Então você pode usar try catchcomo abaixo para capturar os erros e depois passá-los para os manipuladores 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)

Dessa forma, você pode manter sua lógica e manipuladores de erros separados. Além disso, você pode optar por manipular apenas alguns erros e ignorar outros.


Alternativa de atualização : RXJS observável

Vikas Gautam
fonte
4

Use o pacote multipipe para combinar vários fluxos em um fluxo duplex. E lide com erros em um só lugar.

const pipe = require('multipipe')

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


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

Use o padrão Node.js criando uma mecânica de fluxo Transform e chamando seu retorno de chamada donecom um argumento para propagar o erro:

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
fonte
Hmm, então você está dizendo que se todos os processadores de fluxo fossem construídos assim, os erros se propagariam?
BT
-2

O try catch não captura os erros que ocorreram no fluxo, porque eles são lançados após o código de chamada já ter sido encerrado. você pode consultar a documentação:

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

Mehran
fonte
Obrigado, mas isso não responde à pergunta.
BT
Não é útil fornecer um documento de 40 páginas. O que você acha que eu deveria me referir nessa página gigante? Você também leu minha pergunta? Minha pergunta não é "tenta pegar trabalho com fluxos?" Eu já estou bem ciente de que o try-catch não funcionará com erros assíncronos, por exemplo, dos pipelines de processamento de fluxo.
BT