¿Cómo evitar que Node.js salga mientras espera una devolución de llamada?

96

Tengo un código como este:

var client = new mysql.Client(options);
console.log('Icanhasclient');

client.connect(function (err) {
  console.log('jannn');
  active_db = client;
  console.log(err);
  console.log('hest');

  if (callback) {
    if (err) {
      callback(err, null);
    }

    callback(null, active_db);
  }
});

Mi problema es que Node termina inmediatamente cuando lo ejecuto. Imprime 'Icanhasclient', pero no se llama a ninguno de los console.log's dentro de la devolución de llamada.

(mysql en este ejemplo es node-mysql .

¿Hay algo que se pueda hacer para que node.js espere a que se complete la devolución de llamada antes de salir?

mikl
fuente
1
¿A qué te refieres con evitar la salida? Nodejs no sale hasta que se completan todas las devoluciones de llamada. Es un proceso de un solo hilo.
neebz
4
@nEEbz: Si ese fuera el caso, ¿por qué sale mi script sin ejecutar la devolución de llamada?
mikl
Es un problema con la conexión a su base de datos; No estoy muy seguro de por qué no se activa la devolución de llamada. ¿Da algún error?
neebz
No, simplemente falla silenciosamente :(
mikl
Eso es muy raro. Espero que encontremos una respuesta a esto.
neebz

Respuestas:

50

La devolución de llamada no está en cola

El nodo se ejecuta hasta que todas las colas de eventos están vacías. Se agrega una devolución de llamada a una cola de eventos cuando una llamada como

  emmiter1.on('this_event',callback).

ha ejecutado. Esta llamada es parte del código escrito por el desarrollador del módulo.

Si un módulo es un puerto rápido de una versión síncrona / de bloqueo, es posible que esto no suceda hasta que se haya completado una parte de la operación y todas las colas se vacíen antes de que eso suceda, permitiendo que el nodo salga silenciosamente.

Este es un error furtivo, que el desarrollador del módulo podría no encontrar durante el desarrollo, ya que ocurrirá con menos frecuencia en sistemas ocupados con muchas colas, ya que será raro que todos estén vacíos en el momento crítico.

Un posible detector de corrección / error para el usuario es insertar un evento de temporizador especial antes de la llamada a la función sospechosa.

george calvert
fuente
"Esto puede que no suceda hasta que se haya completado una parte de la operación" - ¿Podría aclarar esto? ¿Está diciendo que el nodo podría cerrarse mientras un fragmento de código aún se está ejecutando antes de que tenga la oportunidad de agregar una devolución de llamada a la cola de eventos?
BT
Supongo que quiere decir que si la biblioteca ha convertido código que alguna vez fue síncrono a una nueva versión asincrónica, es posible que los creadores de la biblioteca se hayan olvidado de poner en cola las devoluciones de llamada y las colas de eventos se vacíen en algún lugar en el medio de su ejecución, terminando así sin llamar devoluciones de llamada.
Dielson Sales
1
¿Existe una forma sencilla de reproducir este problema de forma fiable?
Jordan Brough
¡Gracias por esto! Tuve un proceso de "suspensión" y no entendía por qué no se iba. Después de limpiar todos mis oyentes / devoluciones de llamada de eventos, el proceso salió.
ps2goat
7
ADVERTENCIA : a pesar de tener muchos votos a favor, creo que la información en esta respuesta es incorrecta. Llamando EventEmitter.on()no sin añadir nada al bucle de eventos que nodo esperará a que (al menos en la versión actual del nodo). Parece que solo cosas como establecer tiempos de espera y realizar llamadas asíncronas desde la biblioteca central pueden hacer esto. Puede probar esto fácilmente escribiendo un programa de una línea que espera un evento que nunca se disparará. A partir de esta respuesta, se espera también que nunca se termina, sino que lo hace termina al instante. Consulte esta respuesta para obtener más información.
davnicwil
40

Puede simplemente emitir un setTimeout o un tiempo de espera recurrente con setInterval.

Si desea verificar las condiciones de salida, también puede hacer un tiempo de espera condicional:

(function wait () {
   if (!SOME_EXIT_CONDITION) setTimeout(wait, 1000);
})();

Pon esto al final de tu código y la consola simplemente esperará ... y esperará ... hasta que quieras que se cierre.

Todd
fuente
5
Es una buena solución, pero no una solución bonita (lo que no es culpa suya, sino del nodo).
peterh - Reincorpora a Monica
14

Mi solución fue crear una instancia de un EventEmitter y escuchar mi evento personalizado.

var eventEmitter = new process.EventEmitter();

luego llamé eventEmitter.emitdesde la devolución de llamada asíncrona:

client.connect(function (err) {
    eventEmitter.emit('myevent', {something: "Bla"})
});

Lo último en mi guión fue eventEmitter.on:

eventEmitter.on('myevent', function(myResult){
  // I needed the result to be written to stdout so that the calling process could get it
  process.stdout.write(JSON.stringify(myResult));
});

El nodo esperará hasta que el controlador de eventos termine de ejecutarse.

you786
fuente
1
Recibo el error `process.EventEmitter no es un constructor`. ¿Qué sugieres?
Anthony Kong
Busque cómo crear un EventEmitter. coligo.io/nodejs-event-emitter parece un buen recurso. Puede ser que las diferentes versiones de Node hayan cambiado la forma de crear una.
you786
2
EventEmitterla clase está definida por el eventsmódulo. Por lo tanto, para crear un nuevo emisor: const EventEmitter = require('events'); var eventEmitter = new EventEmitter().
CedX
1
ADVERTENCIA : vea mi comentario sobre la respuesta aceptada. Creo que esto está mal y no funciona al menos para la versión actual de node.
davnicwil
5

Basado en la respuesta de @ Todd, creé una sola línea. Inclúyelo al principio de tu guión y configúralo done = truecuando hayas terminado:

var done = (function wait () { if (!done) setTimeout(wait, 1000) })();

Ejemplo:

var done = (function wait () { if (!done) setTimeout(wait, 1000) })();

someAsyncOperation().then(() => {
  console.log('Good to go!');
  done = true;
});

¿Como funciona? Si lo ampliamos un poco:

// Initialize the variable `done` to `undefined`
// Create the function wait, which is available inside itself
// Note: `var` is hoisted but `let` is not so we need to use `var`
var done = (function wait () {

  // As long as it's nor marked as done, create a new event+queue
  if (!done) setTimeout(wait, 1000);

  // No return value; done will resolve to false (undefined)
})();
Francisco Presencia
fuente
1
Por qué está configurando la salida del IIFE, simplemente falso sería mejor con el IIFE después de eso.
tiffon
1
@tiffon Probablemente solo para que sea una sola línea.
Pedro A
0

Aquí están mis dos centavos:

async function main()
{
    await new Promise(function () {});
    console.log('This text will never be printed');
}

function panic(error)
{
    console.error(error);
    process.exit(1);
}

// https://stackoverflow.com/a/46916601/1478566
main().catch(panic).finally(clearInterval.bind(null, setInterval(a=>a, 1E9)));
vbarbarosh
fuente
0

Así es como lo hago. Hago que mi punto de entrada principal devuelva una promesa, luego uso este pequeño contenedor para asegurarme de que mientras esa promesa no se cumpla, el nodo no saldrá:

function wrapPromiseMain(entryPoint) {
    const pollTime = 1000000;
    let interval = setInterval(() => {}, pollTime);
    
    return entryPoint().finally(() => {clearInterval(interval)});
}

Para usarlo, simplemente prometa su punto de entrada principal y páselo como argumento al contenedor:

function main() {

    // ... main stuff ...

    return doSomethingAsync();
}

wrapPromiseMain(main);

Lo encuentro un poco más ordenado que los bucles de sondeo básicos porque cancelará automáticamente el temporizador cuando se establezca la promesa, por lo que no agrega ninguna latencia adicional. Por lo tanto, el tiempo de votación puede ser básicamente para siempre si así lo desea.

ad1c371f7d05
fuente
Nota: editado después de la publicación porque me di cuenta de que podía hacerlo aún más conciso usando setInterval en lugar de llamadas encadenadas a setTimeout.
ad1c371f7d05
-1

Miré la biblioteca felixge / node-mysql y no vi una referencia al comando client.connect en la API. ¿Es esta la llamada real que está tratando de hacer (no tratando de ser quisquilloso aquí)? Independientemente, en mi humilde opinión, debe pensar más en cómo está diseñado Javascript, porque utiliza un paradigma de programación diferente a la mayoría de los otros lenguajes populares.

El primer problema que veo en su código es que no ha definido la devolución de llamada, por lo que en realidad no existe. Asumiría que console.log (devolución de llamada) no está definido. Desde su código, la función anónima es la 'devolución de llamada' para la función client.connect. Debe definir lo que está llamando "devolución de llamada" en un ámbito superior. Por ejemplo, definiré una función myCallback para que exista en el alcance más alto que la función anónima de client.connect. Puede resultar útil buscar el alcance de la variable Javacscript .

    var myCallback(err, response) {
      if (err) {
        console.log('err:%s',err);
      } else {
        console.log('response:%s',response);
      }
    }

    client.connect(err, function(response) {
      // this anonymous function is the callback to client.connect, the var
      // 'callback' would be undefined.
      if (err) {
        myCallback(err);
        return; // Explicit call to return, else the lines below would run.
      } 
      myCallback(null, response);
    });

En segundo lugar, si no llama explícitamente a return dentro de Javascript, la función continuará procesándose. Me picaron por este mismo . Finalmente, Javascript ejecuta un bucle controlado por eventos, lo que significa que nunca esperará a que las funciones devuelvan un valor, por lo que tenemos todas estas devoluciones de llamada en primer lugar. Puede forzar que Javascript se comporte de manera diferente, por ejemplo, usando un ciclo while hasta que una condición sea verdadera. Consulte la biblioteca 'async' de caolan , para conocer varias estrategias de manipulación del bucle de eventos. La principal desventaja del uso excesivo de estos métodos es que en realidad está desperdiciando ciclos / bloqueos de CPU cuando probablemente debería usar más devoluciones de llamada y simplemente repensar cómo funcionan sus programas.

exshovelrydr
fuente
-2

Por favor intente esto. Compruebe si esto ayuda.

var client = new mysql.Client(options);
console.log('Icanhasclient');
var verbose;

if (!verbose) {
    return new Promise(function (resolve, reject) {
        client.connect(function (err) {
            if (err) {
                console.log(Error in connecting
                SQL ${err}
            )
                ;
                return reject(err);
            }
            verbose = client;
            return resolve(verbose);
        })
    })
} else {
    return new Promise(function (resolve) {
        resolve(verbose);
    })
}
Tushar
fuente