可读.destroy()不会同时发出“关闭”和“错误”事件

时间:2019-01-28 18:42:15

标签: javascript node.js events streaming dom-events

我写了一些可能起作用的代码,但我认为readable.destroy()(例如,在我的示例中为src.destroy())应该发出closeerror事件...

下面是一个最小的例子来说明我的困惑:

const fs = require('fs');
const src = fs.createReadStream('streaming-example.js');
const dst = fs.createWriteStream('streaming-example.txt');
src.pipe(dst);
src.on('readable', () => {
  let chunk;
  while (null !== (chunk = src.read())) {
    /**
    * This should cause 'error' and 'close' events to emit
    * @see https://nodejs.org/dist/latest-v11.x/docs/api/stream.html#stream_readable_destroy_error
    */
    src.destroy();
  }
});
src.on('close', () => console.log(`'close' event emitted`));
src.on('end', () => console.log(`'end' event emitted`));
src.on('error', (err) => console.log(`'error' event emitted`));

这是该程序的示例运行:

$ node streaming-example.js 
'close' event emitted
$

(而且恰好还完成了写入名为streaming-example.txt的新文件的操作)

如果不清楚,我希望发出closeerror事件,并依次触发相应的回调。但是,似乎仅发出了close事件。

error事件发生了什么?

1 个答案:

答案 0 :(得分:1)

事实证明,深入研究node.js代码库可以消除这种混淆。

看看node/lib/_stream_readable.js,我们看到destroy函数是在node/lib/internal/streams/destroy.js中定义的。在这里,我们直接通过纯JavaScript得知,error事件仅在将一个真实值传递给destroy函数时才发出(从语义上讲,这是在同一客户端代码中生成的错误) destroy被调用。)

例如,如果我们只是通过更改来修改上面的示例代码

readable.destroy();

成为

readable.destroy(true); // or, more semantically correct, some Error value

我们得到以下输出:

$ node streaming-example.js 
'error' event emitted with err: true
$ 

但是,现在我们丢失了close事件。所以...发生了什么事?

再次查看node/lib/internal/streams/destroy.js,我们注意到以下特殊情况的逻辑:

const readableDestroyed = this._readableState &&
  this._readableState.destroyed;
const writableDestroyed = this._writableState &&
  this._writableState.destroyed;

if (readableDestroyed || writableDestroyed) {
  if (cb) {
    cb(err);
  } else if (err &&
              (!this._writableState || !this._writableState.errorEmitted)) {
    process.nextTick(emitErrorNT, this, err);
  }
  return this;
}

// We set destroyed to true before firing error callbacks in order
// to make it re-entrance safe in case destroy() is called within callbacks

if (this._readableState) {
  this._readableState.destroyed = true;
}

// If this is a duplex stream mark the writable part as destroyed as well
if (this._writableState) {
  this._writableState.destroyed = true;
}

error被正确发出,但close却没有被发出的事实提示我们正在处理双工流。我们可以只是查找一下内容,但为了让操作更简单,请坚持使用计算机告诉我们的内容。用此while循环替换原始的while循环

while (null !== (chunk = src.read())) {
  console.log('before-destroy, src:', JSON.stringify(src));
  src.destroy(true);  // should cause 'close' and 'error' events to emit
  console.log('after-destroy, src:', JSON.stringify(src));       
}

我们得到以下输出:

$ node streaming-example.js 
before-destroy, src: {"_readableState":{"objectMode":false,"highWaterMark":65536,"buffer":{"head":null,"tail":null,"length":0},"length":0,"pipes":{"_writableState":{"objectMode":false,"highWaterMark":16384,"finalCalled":false,"needDrain":false,"ending":false,"ended":false,"finished":false,"destroyed":false,"decodeStrings":true,"defaultEncoding":"utf8","length":633,"writing":true,"corked":0,"sync":false,"bufferProcessing":false,"writelen":633,"bufferedRequest":null,"lastBufferedRequest":null,"pendingcb":1,"prefinished":false,"errorEmitted":false,"emitClose":false,"autoDestroy":false,"bufferedRequestCount":0,"corkedRequestsFree":{"next":null,"entry":null}},"writable":true,"_events":{},"_eventsCount":5,"path":"streaming-example.txt","fd":24,"flags":"w","mode":438,"autoClose":true,"bytesWritten":0,"closed":false},"pipesCount":1,"flowing":false,"ended":false,"endEmitted":false,"reading":true,"sync":false,"needReadable":true,"emittedReadable":false,"readableListening":true,"resumeScheduled":false,"paused":false,"emitClose":false,"autoDestroy":false,"destroyed":false,"defaultEncoding":"utf8","awaitDrain":0,"readingMore":true,"decoder":null,"encoding":null},"readable":true,"_events":{"end":[null,null,null]},"_eventsCount":5,"path":"streaming-example.js","fd":23,"flags":"r","mode":438,"end":null,"autoClose":true,"bytesRead":633,"closed":false}
after-destroy, src: {"_readableState":{"objectMode":false,"highWaterMark":65536,"buffer":{"head":null,"tail":null,"length":0},"length":0,"pipes":{"_writableState":{"objectMode":false,"highWaterMark":16384,"finalCalled":false,"needDrain":false,"ending":false,"ended":false,"finished":false,"destroyed":false,"decodeStrings":true,"defaultEncoding":"utf8","length":633,"writing":true,"corked":0,"sync":false,"bufferProcessing":false,"writelen":633,"bufferedRequest":null,"lastBufferedRequest":null,"pendingcb":1,"prefinished":false,"errorEmitted":false,"emitClose":false,"autoDestroy":false,"bufferedRequestCount":0,"corkedRequestsFree":{"next":null,"entry":null}},"writable":true,"_events":{},"_eventsCount":5,"path":"streaming-example.txt","fd":24,"flags":"w","mode":438,"autoClose":true,"bytesWritten":0,"closed":false},"pipesCount":1,"flowing":false,"ended":false,"endEmitted":false,"reading":true,"sync":false,"needReadable":true,"emittedReadable":false,"readableListening":true,"resumeScheduled":false,"paused":false,"emitClose":false,"autoDestroy":false,"destroyed":true,"defaultEncoding":"utf8","awaitDrain":0,"readingMore":true,"decoder":null,"encoding":null},"readable":true,"_events":{"end":[null,null,null]},"_eventsCount":5,"path":"streaming-example.js","fd":null,"flags":"r","mode":438,"end":null,"autoClose":true,"bytesRead":633,"closed":false}
'error' event emitted
$

这表明我们可能正在处理双工流,或者至少这向我们确切解释了为什么仅发出error事件的原因(因为,特别是仅查看after-destroy输出,this._readableStatethis._writeableState都是真实的,因此destroy函数将局部变量readableDestroyedwriteableDestroyed设置为true,我们从{{ 1}} console.logthis._writableState.errorEmitted,因此false在退出process.nextTick(emitErrorNT, this, err);函数之前就被执行了。)

问题已经得到足够的答案。

另外,最好知道destroy流与某种其他流之间的区别。为此,请快速参考this portion of the node.js documentation


那么,当同时发生duplexclose事件时(例如,当我们不处理error流时)如何处理呢?下面的代码和执行如下所示:

duplex

执行此脚本以及一些I / O(键入const readable = process.stdin; const writable = process.stdout; readable.setEncoding('utf8'); readable.on('readable', () => { let chunk; while ((chunk = readable.read()) !== null) { writable.write(`data: ${chunk}`); } readable.destroy(true); }); readable.on('close', () => console.log(`'close' event emitted`)); readable.on('error', (err) => console.log(`'error' event emitted with err:`, err)); ,然后按回车/确认键)将提供以下输出:

asdf