Wie kann verhindert werden, dass Node.js beendet wird, während auf einen Rückruf gewartet wird?


91

Ich habe folgenden Code:

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

Mein Problem ist, dass der Knoten sofort beendet wird, wenn ich ihn ausführe. Es wird 'Icanhasclient' gedruckt, aber keines der console.logs im Rückruf wird aufgerufen.

(MySQL in diesem Beispiel ist Node-MySQL .

Gibt es etwas, das getan werden kann, damit node.js vor dem Beenden auf den Abschluss des Rückrufs wartet?


1
Was meinst du mit Ausgang verhindern? Nodejs wird erst beendet, wenn alle Rückrufe abgeschlossen sind. Es ist ein einzelner Thread-Prozess.
Neebz

4
@nEEbz: Wenn dies der Fall war, warum wird mein Skript beendet, ohne den Rückruf auszuführen?
Mikl

Es ist ein Problem beim Herstellen einer Verbindung zu Ihrer Datenbank. Ich bin mir nicht sicher, warum der Rückruf nicht ausgelöst wird. Gibt es einen Fehler?
Neebz

Nein, es scheitert nur lautlos :(
mikl

Das ist sehr komisch. Ich hoffe wir finden eine Antwort darauf.
Neebz

Antworten:


49

Rückruf wird nicht in die Warteschlange gestellt

Knoten läuft , bis alle Ereigniswarteschlangen leer sind. Ein Rückruf wird zu einem Ereignis hinzugefügt Warteschlange , wenn ein Anruf , wie beispielsweise

  emmiter1.on('this_event',callback).

ausgeführt hat. Dieser Aufruf ist Teil des vom Modulentwickler geschriebenen Codes.

Wenn es sich bei einem Modul um einen Schnellport aus einer synchronen / blockierenden Version handelt, geschieht dies möglicherweise erst, wenn ein Teil des Vorgangs abgeschlossen ist und alle Warteschlangen zuvor leer sind, sodass der Knoten stillschweigend beendet werden kann.

Dies ist ein hinterhältiger Fehler, auf den der Modulentwickler während der Entwicklung möglicherweise nicht stößt, da er in ausgelasteten Systemen mit vielen Warteschlangen seltener auftritt, da es selten vorkommt, dass alle zum kritischen Zeitpunkt leer sind.

Ein möglicher Fix / Bug-Detektor für den Benutzer besteht darin, vor dem Funktionsaufruf des Verdächtigen ein spezielles Timer-Ereignis einzufügen.


"Dies kann nicht passieren, bis ein Teil der Operation abgeschlossen ist" - Können Sie dies klarstellen? Wollen Sie damit sagen, dass der Knoten möglicherweise beendet wird, während ein Code noch ausgeführt wird, bevor er der Ereigniswarteschlange einen Rückruf hinzufügen kann?
BT

Ich denke, er meint, wenn die Bibliothek Code konvertiert hat, der einmal mit einer neuen asynchronen Version synchron war, haben die Bibliotheksersteller möglicherweise vergessen, die Rückrufe in die Warteschlange zu stellen, und die Ereigniswarteschlangen werden irgendwo in der Mitte Ihrer Ausführung geleert, sodass sie nicht mehr aufgerufen werden Rückrufe.
Dielson Sales

1
Gibt es eine einfache Möglichkeit, dieses Problem zuverlässig zu reproduzieren?
Jordan Brough

Danke dafür! Ich hatte einen "hängenden" Prozess und verstand nicht, warum er nicht aufhören würde. Nachdem alle meine Listener / Event-Rückrufe bereinigt wurden, wurde der Prozess beendet.
ps2goat

7
WARNUNG - Trotz vieler positiver Stimmen denke ich, dass die Informationen in dieser Antwort falsch sind. Der Aufruf EventEmitter.on()ist nicht fügen etwas auf die Ereignisschleife , dass Knoten warten , wird (zumindest in der aktuellen Version von Node). Dies können anscheinend nur Dinge wie das Festlegen von Zeitüberschreitungen und das Tätigen von asynchronen Aufrufen aus der Kernbibliothek. Sie können dies einfach testen, indem Sie ein einzeiliges Programm schreiben, das ein Ereignis erwartet, das niemals ausgelöst wird. Von dieser Antwort würden Sie erwarten, dass sie niemals beendet wird, aber sie wird sofort beendet. Weitere Informationen finden Sie in dieser Antwort .
Davnicwil

38

Sie können mit setInterval einfach ein setTimeout oder ein wiederkehrendes Timeout ausgeben.

Wenn Sie nach Ausgangsbedingungen suchen möchten, können Sie auch eine bedingte Zeitüberschreitung durchführen:

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

Fügen Sie dies am Ende Ihres Codes ein und die Konsole wartet nur ... und wartet ... bis Sie möchten, dass sie geschlossen wird.


4
Es ist eine gute Problemumgehung, aber keine hübsche Lösung (was nicht Ihre Schuld ist, sondern die des Knotens).
Peterh

13

Meine Lösung bestand darin, einen EventEmitter zu instanziieren und auf mein benutzerdefiniertes Ereignis zu warten.

var eventEmitter = new process.EventEmitter();

dann rief ich eventEmitter.emitvom asynchronen Rückruf an:

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

Das Letzte in meinem Skript war 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));
});

Der Knoten wartet dann, bis der Ereignishandler die Ausführung beendet hat.


Ich erhalte "process.EventEmitter" ist kein Konstruktorfehler. Was würdest du vorschlagen?
Anthony Kong

Suchen Sie nach, wie Sie einen EventEmitter erstellen. coligo.io/nodejs-event-emitter scheint eine gute Ressource zu sein. Möglicherweise haben die verschiedenen Versionen von Node die Erstellung einer geändert.
you786

2
EventEmitterKlasse wird vom eventsModul definiert . So erstellen Sie einen neuen Emitter : const EventEmitter = require('events'); var eventEmitter = new EventEmitter().
CedX

1
WARNUNG - siehe meinen Kommentar zur akzeptierten Antwort. Ich denke, das ist falsch und funktioniert zumindest für die aktuelle Version des Knotens nicht.
Davnicwil

3

Basierend auf der Antwort von @ Todd habe ich einen Einzeiler erstellt. Fügen Sie es am Anfang Ihres Skripts ein und legen done = trueSie fest, wann Sie fertig sind:

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

Beispiel:

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

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

Wie funktioniert es? Wenn wir es ein bisschen erweitern:

// 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)
})();

Warum werden Sie auf die Ausgabe des IIFE eingestellt, danach wäre einfach falsch mit dem IIFE besser.
Tiffon

1
@ Tiffon Wahrscheinlich nur, um es zu einem Einzeiler zu machen.
Pedro A

1

Hier sind meine zwei Cent:

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

0

So mache ich es. Ich lasse meinen Haupteinstiegspunkt ein Versprechen zurückgeben und verwende dann diesen kleinen Wrapper, um sicherzustellen, dass der Knoten nicht beendet wird, solange dieses Versprechen nicht erfüllt ist:

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

Um es zu verwenden, versprechen Sie einfach Ihren Haupteinstiegspunkt und übergeben Sie es als Argument an den Wrapper:

function main() {

    // ... main stuff ...

    return doSomethingAsync();
}

wrapPromiseMain(main);

Ich finde es etwas aufgeräumter als die grundlegenden Abfrageschleifen, da es den Timer automatisch abbricht, wenn das Versprechen erfüllt ist, sodass keine zusätzliche Latenz entsteht. Die Abrufzeit kann daher grundsätzlich für immer sein, wenn Sie dies wünschen.


Hinweis: Nach dem Posten bearbeitet, da mir klar wurde, dass ich es mit setInterval noch prägnanter gestalten kann, anstatt verkettete Aufrufe von setTimeout zu verwenden.
ad1c371f7d05

-1

Ich habe mir felixge / node-mysql angesehen Bibliothek angesehen und keinen Verweis auf den Befehl client.connect in der API gefunden. Ist dies der eigentliche Anruf, den Sie tätigen möchten (ohne hier pingelig zu sein)? Unabhängig davon müssen Sie meiner Meinung nach mehr darüber nachdenken, wie Javascript aufgebaut ist, da es ein anderes Programmierparadigma verwendet als die meisten anderen gängigen Sprachen.

Das erste Problem, das ich in Ihrem Code sehe, ist, dass Sie den Rückruf nicht definiert haben, sodass er tatsächlich nicht vorhanden ist. Ich würde annehmen, dass console.log (Rückruf) undefiniert ist. Nach Ihrem Code ist die anonyme Funktion der Rückruf für die Funktion client.connect. Sie müssen definieren, was Sie "Rückruf" in einem höheren Bereich nennen. Zum Beispiel werde ich eine Funktion myCallback so definieren, dass sie in einem höheren Bereich als der anonymen Funktion von client.connect vorhanden ist. Es kann nützlich sein, den Bereich der Javacscript- Variablen nachzuschlagen .

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

Zweitens wird die Funktion weiter verarbeitet, wenn Sie return nicht explizit in Javascript aufrufen. Ich war von diesem gebissen mir . Schließlich führt Javascript eine ereignisgesteuerte Schleife aus, was bedeutet, dass es niemals darauf warten wird, dass Funktionen einen Wert zurückgeben, weshalb wir überhaupt alle diese Rückrufe haben. Sie können Javascript zwingen, sich anders zu verhalten, indem Sie beispielsweise eine while-Schleife verwenden, bis eine Bedingung erfüllt ist. In der 'async'-Bibliothek von caolan finden Sie verschiedene Strategien zur Manipulation der Ereignisschleife. Der Hauptnachteil einer übermäßigen Verwendung dieser Methoden besteht darin, dass Sie tatsächlich CPU-Zyklen / Blockierungen verschwenden, wenn Sie wahrscheinlich mehr Rückrufe verwenden und einfach überdenken sollten, wie Ihre Programme funktionieren.


-1

Bitte versuchen Sie dies. Überprüfen Sie, ob diese Hilfe.

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);
    })
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.