Почему мой Node.js процесс не завершился, как только все слушатели были удалены?

В следующем коде я назначаю слушателю событие data process.stdin с помощью метода once.

console.log('Press Enter to allow process to terminate')
process.stdin.once('data', callback)

function callback (data) {
    console.log('Process can terminate now')
}

Теоретически, когда обратный вызов был запущен, прослушиватель событий должен быть автоматически удален (потому что я привязал его к once), что позволило завершить процесс. Удивительно, но в этом случае процесс никогда не заканчивается (код, который вы видите, это все, попробуйте!). Я также попытался вручную удалить слушателя, но это ничего не меняет.

Есть ли что-то еще, что я не понимаю, возможно?

Ответ 1

Добавление прослушивателя событий data в process.stdin добавляет ссылку на него, которая удерживает процесс открытым. Эта ссылка остается на месте даже после удаления всех прослушивателей событий. Вы можете сделать это вручную unref() в своем обратном вызове, например:

console.log('Press Enter to allow process to terminate')
process.stdin.once('data', callback)

function callback (data) {
    console.log('Process can terminate now')
    process.stdin.unref()
}

Кроме того, в качестве общего инструмента для отладки для таких вещей есть две (недокументированные) функции, которые вы можете вызвать, чтобы получить список вещей, поддерживающих ваш процесс:

process._getActiveHandles()
process._getActiveRequests()

Смотрите этот запрос на растяжение в проекте node для фона.


Обновление:. Вы спросили о подключении слушателей событий после unref() 'd process.stdin. Вот краткий пример, показывающий, что слушатель сам присоединяется и работает:

console.log('Press Enter to allow process to terminate')
process.stdin.once('data', callback)

function callback (data) {
    console.log('Unreferencing stdin. Exiting in 5 seconds.')
    process.stdin.unref()

    process.stdin.once('data', function(data) {
        console.log('More data')
    })

    setTimeout(function() {
        console.log('Timeout, Exiting.')
    }, 5000);
}

С помощью этого кода, если вы нажмете еще одну клавишу перед тем, как срабатывает setTimeout (5 секунд), вы увидите вывод More data на консоль. После срабатывания обратного вызова setTimeout процесс завершится. Фокус в том, что setTimeout создает таймер, который также сохраняет ссылку. Поскольку процесс все еще имеет ссылку на что-то, он не будет сразу уходить. Как только таймер срабатывает, эта ссылка высвобождается, и процесс завершается. Это также показывает, что ссылки добавляются (и удаляются) к вещам, которые им нужны автоматически (таймер, созданный setTimeout в этом случае).

Ответ 2

Просто вызовите .end в потоке process.stdin

Для меня это более простой (и documented) способ завершения потока.

console.log('Press Enter to allow process to terminate');
process.stdin.once('data', callback);

function callback (data) {
  console.log('Process can terminate now');
  process.stdin.end();
}

Также стоит отметить, что node устанавливает поток как контекст для функции обратного вызова, поэтому вы можете просто вызвать this.end

console.log('Press Enter to allow process to terminate');
process.stdin.once('data', callback);

function callback (data) {
  // `this` refers to process.stdin here
  console.log('Process can terminate now');
  this.end();
}

Вы также можете испустить событие end, которое имеет дополнительные преимущества, такие как возможность вызова функции, когда поток завершен.

console.log('Press Enter to allow process to terminate');

process.stdin.once('data', function(data) {
  console.log('Process can terminate now');
  this.emit("end");
});

process.stdin.on('end', function() {
  console.log("all done now");
});

Это приведет к выводу

Press Enter to allow process to terminate

Process can terminate now
all done now

Конечным решением было бы использовать process.exit. Это позволяет вам прекратить выполнение программы, когда захотите.

for (var i=0; i<10; i++) {
  process.stdout.write( i.toString() );
  if (i > 3) process.exit();
}

Выход

01234

Это будет работать внутри обратного вызова потока, как часть дочернего процесса или любого другого кода.