Jak zapobiec zamykaniu Node.js podczas oczekiwania na oddzwonienie?


96

Mam taki kod:

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

Mój problem polega na tym, że Node kończy pracę natychmiast po uruchomieniu. Wyświetla „Icanhasclient”, ale żaden z elementów console.log wewnątrz wywołania zwrotnego nie jest wywoływany.

(mysql w tym przykładzie to node-mysql .

Czy jest coś, co można zrobić, aby node.js czekał na zakończenie wywołania zwrotnego przed zakończeniem?


1
co masz na myśli, aby zapobiec wyjściu? Nodejs nie wychodzi, dopóki wszystkie wywołania zwrotne nie zostaną zakończone. Jest to proces z jednym wątkiem.
— neebz

4
@nEEbz: Jeśli tak było, dlaczego mój skrypt kończy pracę bez wykonania wywołania zwrotnego?
— mikl

Jest jakiś problem z połączeniem się z twoją bazą danych; Nie jestem pewien, dlaczego nie uruchamia wywołania zwrotnego. Czy daje jakiś błąd?
— neebz

Nie, po prostu cicho zawodzi :(
— mikl

To bardzo dziwne. Mam nadzieję, że znajdziemy na to odpowiedź.
— neebz

Odpowiedzi:


50

Oddzwanianie nie jest w kolejce

Węzeł działa, dopóki wszystkie kolejki zdarzeń nie będą puste. Wywołanie zwrotne jest dodawane do kolejki zdarzeń, gdy wywołanie, takie jak

  emmiter1.on('this_event',callback).

wykonał. To wywołanie jest częścią kodu napisanego przez programistę modułu.

Jeśli moduł jest szybkim portem z wersji synchronicznej / blokującej, może się to nie zdarzyć, dopóki pewna część operacji nie zostanie zakończona, a wszystkie kolejki mogą zostać opróżnione przed tym, co pozwoli węzłowi na ciche wyjście.

Jest to podstępny błąd, na który twórca modułu może nie natknąć się podczas programowania, ponieważ będzie się on pojawiał rzadziej w obciążonych systemach z wieloma kolejkami, ponieważ rzadko wszystkie z nich będą puste w krytycznym momencie.

Możliwą poprawką / wykrywaniem błędów przez użytkownika jest wstawienie specjalnego zdarzenia czasowego przed wywołaniem podejrzanej funkcji.


„może się to nie zdarzyć, dopóki nie zakończy się pewna część operacji” - czy możesz to wyjaśnić? Czy chcesz powiedzieć, że węzeł może zakończyć pracę, gdy fragment kodu nadal działa, zanim będzie miał szansę dodać wywołanie zwrotne do kolejki zdarzeń?
— BT

Chyba chodzi mu o to, że jeśli biblioteka przekonwertowała kod, który kiedyś był zsynchronizowany z nową wersją asynchroniczną, to możliwe, że twórcy biblioteki zapomnieli o kolejce wywołań zwrotnych i kolejki zdarzeń zostaną opróżnione gdzieś w połowie wykonywania, kończąc tym samym na nieuprawnionym oddzwonienia.
— Dielson Sales

1
Czy istnieje łatwy sposób na niezawodne odtworzenie tego problemu?
— Jordan Brough

Dzięki za to! Miałem proces „zawieszania się” i nie rozumiałem, dlaczego się nie kończy. Po wyczyszczeniu wszystkich moich detektorów / wywołań zwrotnych zdarzeń proces zakończył się.
— ps2goat

7
OSTRZEŻENIE - Pomimo wielu głosów za, myślę, że informacje w tej odpowiedzi są nieprawidłowe. Wywołanie EventEmitter.on()ma nie dodawać nic na pętli zdarzeń, że węzeł będzie czekać na (przynajmniej w obecnej wersji węzła). Wydaje się, że tylko takie rzeczy, jak ustawianie limitów czasu i wykonywanie wywołań asynchronicznych z podstawowej biblioteki, mogą to zrobić. Możesz to łatwo przetestować, pisząc program jednowierszowy, który oczekuje zdarzenia, które nigdy się nie uruchomi. Od tej odpowiedzi, można by oczekiwać, że nigdy nie zakończyć, ale nie rozwiązać natychmiast. Zobacz tę odpowiedź, aby uzyskać więcej informacji.
— davnicwil

40

Możesz po prostu wydać setTimeout lub cykliczny limit czasu za pomocą setInterval.

Jeśli chcesz sprawdzić warunki wyjścia, możesz również ustawić warunkowy limit czasu:

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

Umieść to na końcu swojego kodu, a konsola będzie po prostu czekać ... i czekać ... aż zechcesz ją zamknąć.


5
To dobre obejście, ale nie ładne rozwiązanie (co nie jest twoją winą, ale node).
— peterh - Przywróć Monikę

14

Moim rozwiązaniem było utworzenie wystąpienia EventEmitter i nasłuchiwanie mojego niestandardowego zdarzenia.

var eventEmitter = new process.EventEmitter();

następnie zadzwoniłem eventEmitter.emitz wywołania zwrotnego asynchronicznego:

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

Ostatnią rzeczą w moim skrypcie było 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));
});

Węzeł będzie wtedy czekał, aż program obsługi zdarzeń zakończy działanie.


1
Otrzymuję komunikat „process.EventEmitter” nie jest konstruktorem. Co byś zasugerował?
— Anthony Kong,

Zobacz, jak utworzyć EventEmitter. coligo.io/nodejs-event-emitter wydaje się dobrym źródłem informacji. Możliwe, że różne wersje Node zmieniły sposób tworzenia.
— you786

2
EventEmitterklasa jest definiowana przez eventsmoduł. Tak, aby utworzyć nowy nadajnik: const EventEmitter = require('events'); var eventEmitter = new EventEmitter().
— CedX

1
UWAGA - zobacz mój komentarz do zaakceptowanej odpowiedzi. Myślę, że to jest złe i nie działa przynajmniej dla obecnej wersji node.
— davnicwil

5

Bazując na odpowiedzi @ Todda, stworzyłem jednolinijkowy tekst. Uwzględnij go na początku swojego skryptu i ustaw, done = truegdy skończysz:

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

Przykład:

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

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

Jak to działa? Jeśli trochę go rozszerzymy:

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

1
Dlaczego ustawiasz wartość na wyjściu IIFE, po prostu fałszywe byłoby lepsze w przypadku IIFE.
— tiffon

1
@tiffon Prawdopodobnie tylko po to, aby uczynić go jednym wierszem.
— Pedro A

0

Oto moje dwa centy:

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

Oto jak to robię. Sprawiam, że mój główny punkt wejścia zwraca obietnicę, a następnie używam tego małego opakowania, aby upewnić się, że dopóki ta obietnica nie zostanie uregulowana, węzeł nie wyjdzie:

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

Aby go użyć, po prostu obiecaj swój główny punkt wejścia i przekaż go jako argument do opakowania:

function main() {

    // ... main stuff ...

    return doSomethingAsync();
}

wrapPromiseMain(main);

Uważam, że jest to trochę uporządkowane niż podstawowe pętle odpytywania, ponieważ automatycznie anuluje licznik czasu, gdy obietnica się ustabilizuje, więc nie dodaje żadnego dodatkowego opóźnienia. Dlatego czas odpytywania może być zasadniczo wieczny, jeśli chcesz.


Uwaga: edytowane po wysłaniu, ponieważ zdałem sobie sprawę, że mogę uczynić to jeszcze bardziej zwięzłym, używając setInterval zamiast połączonych wywołań setTimeout.
— ad1c371f7d05

-1

Spojrzałem na bibliotekę felixge / node-mysql i nie widziałem odniesienia do polecenia client.connect w API. Czy to jest właściwa rozmowa, którą próbujesz wykonać (nie próbując być tu drapieżnym)? Niezależnie od tego, IMHO musisz więcej pomyśleć o tym, jak został zaprojektowany Javascript, ponieważ używa on innego paradygmatu programowania niż większość innych popularnych języków.

Pierwszą kwestią, jaką widzę w Twoim kodzie, jest to, że nie zdefiniowałeś wywołania zwrotnego, więc w rzeczywistości nie istnieje. Zakładam, że console.log (callback) jest niezdefiniowana. W kodzie funkcja anonimowa to „wywołanie zwrotne” dla funkcji client.connect. Musisz zdefiniować to, co nazywasz „oddzwonieniem” w wyższym zakresie. Na przykład zdefiniuję funkcję myCallback, aby istniała w zakresie wyższym niż funkcja anonimowa client.connect. Przydatne może być wyszukanie zakresu zmiennych 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);
    });

Po drugie, jeśli nie wywołasz w sposób jawny funkcji return w JavaScript, funkcja będzie kontynuować przetwarzanie. I został ugryziony przez ten sam . Wreszcie, Javascript uruchamia pętlę sterowaną zdarzeniami, co oznacza, że ​​nigdy nie będzie czekać, aż funkcje zwrócą wartość, dlatego w pierwszej kolejności mamy wszystkie te wywołania zwrotne. Możesz wymusić inne zachowanie JavaScript, na przykład używając pętli while do momentu spełnienia warunku. Zobacz bibliotekę „async” firmy caolan , aby poznać różne strategie manipulowania pętlą zdarzeń. Główną wadą nadużywania tych metod jest faktyczne marnowanie cykli / blokowania procesora, gdy prawdopodobnie powinieneś użyć więcej wywołań zwrotnych i po prostu ponownie przemyśleć, jak działają twoje programy.


-2

Spróbuj tego. Sprawdź, czy to pomoże.

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);
    })
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.