使用node.js流进行错误处理


164

处理流错误的正确方法是什么?我已经知道有一个“错误”事件可以听,但是我想了解有关任意复杂情况的更多详细信息。

对于初学者,要制作简单的管道链时该怎么做:

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

以及如何正确创建这些转换之一,以便正确处理错误?

更多相关问题:

  • 当发生错误时,“结束”事件会怎样?它永远不会被解雇吗?有时会被解雇吗?它取决于转换/流吗?这里的标准是什么?
  • 有没有通过管道传播错误的机制?
  • 域可以有效解决此问题吗?例子会很好。
  • 由“错误”事件引起的错误是否具有堆栈跟踪?有时?决不?有没有办法从他们那里得到一个?

1
这不是小事。Promise框架使其变得更加简单
slezica 2014年

27
不幸的是,承诺/未来并不能真正帮助您解决流媒体问题
BT

Answers:


221

转变

转换流既可读又可写,因此确实是很好的“中间”流。因此,有时将它们称为through流。它们在这种方式上类似于双工流,不同之处在于它们提供了一个不错的接口来操纵数据,而不仅仅是发送数据。转换流的目的是操纵通过流传输通过流的数据。例如,您可能要进行一些异步调用,或者派生几个字段,重新映射一些内容,等等。


您可能在哪里放置转换流


有关如何创建转换流的信息,请参见此处此处。您所要做的就是:

  1. 包括流模块
  2. 实例化(或继承自)Transform类
  3. 实现一个_transform需要一个的方法(chunk, encoding, callback)

块就是您的数据。在大多数情况下,如果您使用,就无需担心编码objectMode = true。处理完块后,将调用回调。然后将这一块推送到下一个流。

如果您想要一个很好的帮助程序模块,它将使您真正非常轻松地完成流操作,我建议through2

对于错误处理,请继续阅读。

在管道链中,处理错误确实是不平凡的。根据该线程,不会构建.pipe()来转发错误。所以像...

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

...只会侦听流中的错误c。如果在上发出了错误事件a,则该事件不会传递,实际上会抛出。要正确执行此操作:

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

现在,尽管第二种方法较为冗长,但您至少可以保留错误发生位置的上下文。这通常是一件好事。

我发现一个库对您有帮助,但是如果您只想捕获目标位置的错误,而又不太关心事件发生的地方,那就是事件流

结束

当引发错误事件时,将不会(明确地)引发结束事件。错误事件的发出将结束流。

以我的经验,域名在大多数情况下都非常有效。如果您有未处理的错误事件(即在没有侦听器的情况下在流上发出错误),则服务器可能会崩溃。现在,正如上面的文章所指出的,您可以将流包装在应该正确捕获所有错误的域中。

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

域的优点在于,它们将保留堆栈跟踪。尽管事件流在这方面也做得很好。

要进一步阅读,请查阅流手册。很深入,但是非常有用,并且提供了许多有用模块的出色链接。


这真是很棒的信息,谢谢!您能否添加一些有关为什么要创建转换流以及为什么它与我的问题相关的内容?
英国电信

当然-尽管我自从你问起它以来就认为它是相关的; )
mshell_lauren 2014年

1
isaccs在Google网上论坛-nodejs上发布的信息:groups.google.com/d/msg/nodejs/lJYT9hZxFu0/L59CFbqWGyYJ(不是grokbase)
2014年

这个答案写得很完美。我将研究域名建议-这似乎是我正在寻找的解决方案。
分号

12
请注意,您无需将.on('error')处理程序包装在匿名函数中,即a.on('error', function(e){handleError(e)})可以是a.on('error', handleError)
timoxley15

28

如果使用的节点> = v10.0.0,则可以使用stream.pipelinestream.finished

例如:

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

有关更多讨论,请参见此github PR


1
finished如果pipeline已经有回调,为什么还要使用呢?
马科斯·佩雷拉

4
您可能希望在管道和各个流之间以不同的方式处理错误。
shusson

25

域已弃用。你不需要它们。

对于此问题,转换或可写之间的区别不是那么重要。

mshell_lauren的答案很好,但是作为替代,您还可以在您认为可能会出错的每个流上显式侦听错误事件。并根据需要重用处理程序函数。

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

这样做可以防止臭名昭著的未捕获异常(如果其中一个流触发其错误事件)


3
大声笑有乐趣地处理3个不同的错误事件,并祈祷写3个不同的流媒体库的人正确实现了错误处理
Alexander Mills

4
@Alex Mills 1)处理3个事件的问题是什么,当它们的类型相同时error,为什么它们“不同”-因此,每个事件都是不同的事实也可以解决;2)除了原生Node.js功能以外,上面还写了哪些流媒体库?和3)当内部显然可以允许任何人在已经存在的事件之上附加其他错误处理程序时,它们在内部如何处理事件有什么关系呢?
2013年

10

可以使用一个简单的函数将来自整个链的错误传播到最右边的流:

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

可以像这样使用:

safePipe(readable, [ transform1, transform2, ... ]);

5

.on("error", handler)仅处理Stream错误,但是如果您使用自定义Transform流,.on("error", handler)请不要捕获_transform函数内部发生的错误。因此,可以执行以下操作来控制应用程序流程:-

this_transform函数中的关键字指向Stream自身,即一个EventEmitter。因此,您可以使用try catch如下所示的错误来捕获错误,然后再将其传递给自定义事件处理程序。

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

这样,您可以将逻辑处理程序和错误处理程序分开。此外,您可以选择仅处理某些错误而忽略其他错误。

更新
替代:RXJS可观察


4

使用多管道程序包将多个流合并为一个双工流。并在一处处理错误。

const pipe = require('multipipe')

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


// centralized error handling
stream.on('error', fn)

1

通过创建Transform流机制并done使用参数调用其回调来使用Node.js模式,以传播错误:

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

嗯,所以你是说如果所有流处理器都是这样构建的,错误会传播吗?
BT

-2

尝试catch不会捕获流中发生的错误,因为在调用代码已经退出之后会抛出这些错误。您可以参考文档:

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


谢谢,但这根本不能回答问题。
英国电信

给我一个40页的文档没有帮助。您认为我应该在那个大页面上指什么?另外,你读过我的问题吗?我的问题不是“是否尝试使用流?我已经很清楚,try-catch不适用于异步错误,例如来自流处理管道的错误。
BT
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.