Как предотвратить выход из цикла событий NodeJS?

Я реализовал собственную функцию, которая выполняет обратный вызов. NodeJS знает интерфейс, но он ничего не знает о его реализации. Эта нативная функция получает обратный вызов и будет вызывать его, когда результат будет готов. Я не хочу, чтобы цикл цикла был завершен, пока обратный вызов не был вызван.

Вот пример такой проблемы.

В настоящее время мне нужно сделать некоторые операции ввода-вывода (даже если это немой тайм-аут), чтобы заставить NodeJS ждать моей функции.

В Boost.Asio Я просто создаю объект work и уничтожаю его, когда вызывается обратный вызов. Цикл событий Boost.Asio не выходил, пока этот объект хранится. Есть ли аналогичный подход для NodeJS? Что я использую в NodeJS (бонус, если ваш ответ не упоминает таймеры)?

Ответ 1

Лучший способ сделать это - написать аддон С++ и использовать один обрабатывает, предлагаемые libuv (конечно, тот, который соответствует вашим требованиям), см. официальную документацию для более подробной информации).

Если вы не хотите этого делать, или если вы не можете этого сделать (это так, если я правильно понял вопрос), жизнеспособное решение, не упомянутое в других ответах, использует process.nextTick запланировать функцию, которая проверяет каждый тик, если цикл может истекать или нет.
см. здесь для более подробной информации о process.nextTick.

Как минимальный, рабочий, бесконечный пример:

var process = require('process')
var stop = false;
var f = function() { if(!stop) process.nextTick(f) }
f()

Таким образом, ваша функция отвечает за настройку управляющей переменной stop после ее завершения, тогда цикл остановится.

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

А плюс использования выделенной функции, запланированной на следующий тик, заключается в том, что читателю ясно, что вы делаете. С другой стороны, поддельный сервер, тайм-аут, запланированный в будущем, или поток ввода-вывода, возобновленный и никогда не используемый, довольно неясны, так как читатель не сразу узнает, почему вы это делаете.

Ответ 2

Способ 1. Создайте фиктивный сервер (обновленный для множественного обратного вызова):

var counter = 0;

var server = require('net').createServer().listen(); // <-- Dummy server
console.log('Dummy server start.');

var ffi = require('ffi'),
    ref = require('ref')

var lib = ffi.Library('./libffi_async_demo', {
  'print_thread_id': [ 'void', [] ],
  'run_delayed': [ 'void', [ 'pointer' ] ],
});

var checkExit = function (){
  counter--;
  if (counter===0) {
    server.close(); // <-- exit Dummy Server
    console.log('Dummy server stop.');
  }
}

// Wrapper for lib.run_delay()
run_delay = function(cb) {
  counter++; // <-- increase counter
  lib.run_delayed(cb);
}

var callback1 = ffi.Callback('void', [], function() {
  console.log("js callback1 started");
  lib.print_thread_id();
  console.log("js callback1 finished");

  checkExit(); // <-- call at the end of each callback
})

var callback2 = ffi.Callback('void', [], function() {
  console.log("js callback2 started");
  lib.print_thread_id();
  console.log("js callback2 finished");

  checkExit(); // <-- call at the end of each callback
})

var callback3 = ffi.Callback('void', [], function() {
  console.log("js callback3 started");
  lib.print_thread_id();
  console.log("js callback3 finished");

  checkExit(); // <-- call at the end of each callback
})

run_delay(callback1); // use wrapper
run_delay(callback2); // use wrapper
run_delay(callback3); // use wrapper

Способ 2: длительный тайм-аут, процесс завершения обратного вызова

var timeout; // Hold timeout reference from setTimeout()

var ffi = require('ffi'),
    ref = require('ref')

var lib = ffi.Library('./libffi_async_demo', {
  'print_thread_id': [ 'void', [] ],
  'run_delayed': [ 'void', [ 'pointer' ] ],
});

var callback = ffi.Callback('void', [], function() {
  console.log("js callback started");
  lib.print_thread_id()
  console.log("js callback finished");

// Use one of the following 3:

//timeout.unref(); // <-- remove timer from Node event loop
//require('process').exit(); //<-- end process
  clearTimeout(timeout); // <-- cancel timer

})

lib.run_delayed(callback)

timeout = setTimeout(function() { }, 3600000); // <-- reasonably long timeout, eg. 1hr

Ответ 3

Начните замену где-нибудь в своем коде, это предотвратит выход вашего приложения.

const repl = require('repl');
repl.start('> ')

Когда вы закончите, просто позвоните

process.exit()

https://nodejs.org/api/repl.html

Ответ 4

Вы также можете использовать stdin Readable stream, чтобы удерживать выход из цикла.

const callback = ffi.Callback('void', [], function() {
  // do your stuff here

  // switch stream out of flowing mode.
  process.stdin.pause();
});

// set stream into flowing mode ("old mode")
process.stdin.resume();

lib.run_delayed(callback);

Ссылка: примечание в https://nodejs.org/api/process.html#process_process_stdin

Ответ 5

создать большой тайм-аут - это предотвратит выход из node, а также защитит вас от неопределенного ожидания (для внешнего результата node):

var ffi = require('ffi'),
    ref = require('ref')

var ffiTimeout;
var lib = ffi.Library('./libffi_async_demo', {
  'print_thread_id': [ 'void', [] ],
  'run_delayed': [ 'void', [ 'pointer' ] ],
});

var ffiDidTimedOut = false;
var cancelFfi = function(timeoutCb) {
  return function() {
    ffiDidTimedOut = true;
    timeoutCb();
  }
}

var callback = ffi.Callback('void', [], function() {
  if (ffiDidTimedOut) {
    return; // sorry, we waited too long and already started doing something else
  }
  // all good, async ffi finished within expected time and we are back in our js land
  clearTimeout(ffiTimeout);
  lib.print_thread_id()
})

lib.run_delayed(callback)

// continueIfCancelledCallback is your continuation "what to do id ffi actually takes more than 20 seconds to run"
ffiTimeout = setTimeout(cancelFfi(continueIfCancelledCallback), 20000); // 20 seconds