javascript - 可读.destroy() 不发出 'close' 和 'error' 事件 Node.js

标签 javascript node.js events streaming dom-events

我编写了一些恰好可以工作的代码,但我认为应该使用 read.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 事件发射发生了什么?

最佳答案

事实证明,通过深入了解 Node.js 代码库可以阐明这种困惑。

看看node/lib/_stream_readable.js ,我们看到destroy函数定义在node/lib/internal/streams/destroy.js中。在这里,我们直接被告知(通过普通的 javascript),只有当恰好有一个真值被传递到 destroy 函数时,才会发出 error 事件(从语义上讲,这将是在同一个函数中生成的错误)调用 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 都是 true,因此 destroy 函数设置局部变量readDestroyedwriteableDestroyed 为 true,我们从 console.log 中注意到 this._writableState.errorEmissedfalse,因此 process.nextTick(emitErrorNT, this, err); 在退出 destroy 函数之前执行。

问题现已得到充分解答。

另外,最好了解一下双工流与其他类型的流之间的区别。为此,快速引用this portion of the node.js documentation是一个开始。


那么,当 closeerror 事件同时发出时(即,当我们不处理 duplex 时)怎么样? > Stream?下面的代码和执行就是这样做的,如下所示:

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

执行此脚本以及一些 I/O(输入 asdf 然后按回车/回车键)将提供以下输出:

$ node streaming-example2.js 
asdf
data: asdf
'error' event emitted with err: true
'close' event emitted
$

关于javascript - 可读.destroy() 不发出 'close' 和 'error' 事件 Node.js,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54408334/

相关文章:

javascript - 如何从“通知我”字段中获取电子邮件

node.js - findByID 不是函数

javascript - Webpack 4 和 Uglify 插件(TypeError : Cannot read property 'length' of undefined)

c# - 如何在外观类中订阅事件

excel - 如何将目标传递给 Excel 事件中的另一个宏?

javascript - 在 View 中使用编译指令和 ng-repeat 的组合可防止正确更新范围元素列表

javascript - Stripe/node.js : how retrieve stripe subscription safely + increment 1

javascript - 发布新的 Vue 应用程序版本时清除 chrome 中的缓存

node.js - pg-promise 创建列错误

jquery - 将 timeupdate 事件与 div 绑定(bind)