readable.destroy () не генерирует события 'close' и 'error' Node.js - PullRequest
0 голосов
/ 28 января 2019

Я написал некоторый код, который работает, но я подумал, что readable.destroy() (например, src.destroy(), в моем примере) должен выдавать close и error события ...

Вот несколько минимальный пример, чтобы проиллюстрировать мою путаницу:

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)

Если неясно, я ожидал, что события close и error будут отправлены и, в свою очередь, вызвать соответствующие обратные вызовы.Однако только событие close, по-видимому, было отправлено.

Что случилось с эмиссией события error?

1 Ответ

0 голосов
/ 28 января 2019

Оказывается, что взятие пика в базе кода node.js объясняет эту путаницу.

Взглянув на node / lib / _stream_readable.js , мы видим, что функция destroyопределяется в узел / lib / internal / streams / destroy.js .Здесь нам прямо говорят (через простой javascript), что событие error генерируется только тогда, когда в функцию уничтожения передается ровно одно истинное значение (что семантически будет ошибкой, сгенерированной в том же клиентском коде, что и * 1008).* вызывается в).

Например, если мы просто изменим приведенный выше пример кода, изменив

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._readableState и this._writeableState являются правдивыми, и поэтому функция destroy устанавливает локальноепеременные readableDestroyed и writeableDestroyed равны true, и из console.log мы замечаем, что this._writableState.errorEmitted равно false, поэтому process.nextTick(emitErrorNT, this, err); выполняется непосредственно перед выходом из функции destroy).

На этот вопрос уже получен достаточный ответ.

В качестве дополнительной информации полезно знать, в чем отличие потока duplex от потока другого типа.Для этого, краткий справочник по этой части документации по node.js является началом.


Итак, как насчет примера, когда close и errorгенерируются события (т. е. когда мы не имеем дело с duplex потоком? Следующий код и выполнение делают это, как показано ниже:

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 с последующим нажатием клавиши возврата / ввода), выдает следующий вывод:

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