Wie kann man den Thread in node.js in den Ruhezustand versetzen, ohne andere Threads zu beeinflussen?

74

Gemäß der Ereignisschleife node.js Legendes , node.js unterstützt ein einzelnes Thread - Modell. Das heißt, wenn ich mehrere Anforderungen an einen node.js-Server stelle, wird nicht für jede Anforderung ein neuer Thread erzeugt, sondern jede Anforderung einzeln ausgeführt. Wenn ich für die erste Anforderung in meinem node.js-Code Folgendes tue und währenddessen eine neue Anforderung auf dem Knoten eingeht, muss die zweite Anforderung warten, bis die erste Anforderung abgeschlossen ist, einschließlich einer Ruhezeit von 5 Sekunden. Richtig?

var sleep = require('sleep');
    sleep.sleep(5)//sleep for 5 seconds

Gibt es eine Möglichkeit, wie node.js für jede Anforderung einen neuen Thread erzeugen kann, damit die zweite Anforderung nicht auf den Abschluss der ersten Anforderung warten muss, oder kann ich den Ruhezustand nur für einen bestimmten Thread aufrufen?

emilly
quelle
4
du musst nicht so schlafen. Sie sollten stattdessen einen Blick auf setTimeout werfen -> nodejs.org/api/globals.html#globals_settimeout_cb_ms
Alfred

Antworten:

154

Wenn Sie sich auf den Ruhezustand des npm-Moduls beziehen , wird in der Readme-Datei darauf hingewiesen, dass sleepdie Ausführung blockiert wird. Sie haben also Recht - es ist nicht das, was Sie wollen. Stattdessen möchten Sie setTimeout verwenden, das nicht blockiert. Hier ist ein Beispiel:

setTimeout(function() {
  console.log('hello world!');
}, 5000);

Für alle, die dies mit es7 async / await tun möchten, sollte dieses Beispiel helfen:

const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));

const example = async () => {
  console.log('About to snooze without halting the event loop...');
  await snooze(1000);
  console.log('done!');
};

example();
David Weldon
quelle
Daher führt node.js jeweils eine Anforderung aus, was bedeutet, dass es einem Single-Threaded-Modell folgt. Richtig?
Emilly
7
Ja, es wird ein einzelner Ausführungsthread verwendet, sodass jeweils nur eine Anforderung berechnet werden kann, bei vielen Anforderungen jedoch Ereignisse ausstehen können. Wenn Sie anrufen setTimeout, speichert es das Ereignis in der Warteschlange und kann dann mit der Ausführung der nächsten Anforderung beginnen. Nach fünf Sekunden wird das Ereignis aus der Warteschlange entfernt und der Rückruf ausgeführt (die console.login diesem Fall enthaltene Funktion ).
David Weldon
In diesem Screencast erfahren Sie mehr darüber, wie Node.js mehrere Anforderungen ausführt, ohne dass diese aufeinander warten.
Michelle Tilley
6
Der Thread wird nicht blockiert. Der Code nach diesem setTimeout wird fortgesetzt, und nach 5 Sekunden wird auf der Konsole "Hallo Welt" gedruckt.
CodeFarmer
1
@DavidWeldon, Wie entscheidet Node, dass setTimeout irgendwann ausgelöst wird, nachdem es aus der Warteschlange entfernt wurde? Mit anderen Worten, was veranlasst die Funktion, nach fünf Sekunden ausgeführt zu werden, wenn sie sich nicht im Ausführungsmodus / -kontext befindet?
Securecurve
1

Wenn Sie eine Schleife mit einer asynchronen Anforderung in jeder haben und zwischen jeder Anforderung eine bestimmte Zeit möchten, können Sie diesen Code verwenden:

   var startTimeout = function(timeout, i){
        setTimeout(function() {
            myAsyncFunc(i).then(function(data){
                console.log(data);
            })
        }, timeout);
   }

   var myFunc = function(){
        timeout = 0;
        i = 0;
        while(i < 10){
            // By calling a function, the i-value is going to be 1.. 10 and not always 10
            startTimeout(timeout, i);
            // Increase timeout by 1 sec after each call
            timeout += 1000;
            i++;
        }
    }

In diesem Beispiel wird nach jeder Anforderung 1 Sekunde gewartet, bevor die nächste gesendet wird.

Jodo
quelle
-1. Dies wird NICHT das tun, was Sie erwarten. Die Schleife läuft NICHT asynchron. Alle Anfragen werden sofort ausgelöst. Seien Sie sehr vorsichtig, wenn Sie an eine asynchrone Schleife denken - diese Kombination ist schwierig.
Treecoder
Natürlich wird die Schleife auf einmal aufgerufen. Das Timout erhöht jedoch jede Schleife. Also 1 Sek ... 2 Sek ... 3 Sek. Aus diesem Grund wird "myAsyncFunc" nicht alle auf einmal, sondern jede Sekunde aufgerufen. Es funktioniert wie erwartet, wenn ich es teste. Können Sie mir sagen, was Ihrer Meinung nach daran falsch ist?
Jodo
1
Der Code funktioniert übrigens etwas knifflig. Die Funktion startTimeout wird sofort zehnmal aufgerufen, aber jeder Aufruf hat eine um eine Sekunde größere Zeitüberschreitung, sodass jeder Aufruf von myAsyncFunc eine Sekunde später aufgerufen wird.
Greuze
1
Aber das macht den ganzen Zweck der Schleife zunichte. Sie sollten entscheiden können, wie oft die Schleife ausgeführt wird. Was Sie oben tun, führt die Schleife genau 10 Mal aus - unabhängig davon, ob Sie 10 Iterationen benötigt haben oder nicht. Sie können nicht entscheiden, ob Sie am Ende der vorherigen Iteration eine weitere Iteration wünschen oder nicht - da Sie nicht wissen, wann das Ende der vorherigen Iteration stattfinden wird. Daher müssen Sie im Voraus entscheiden, wie viele Iterationen auftreten sollen. Die Logik zum Unterbrechen der Iteration wird dann in den Betreff-Rückruf übertragen - myAsyncFuncin Ihrem Fall.
Treecoder
1
Der zweite Fehler besteht darin, dass Sie davon ausgehen, dass die Ausführung Ihres Rückrufs nicht länger als 1 Sekunde dauert. Was ist, wenn Ihre nächste Iteration vom Ergebnis Ihrer vorherigen Iteration abhängt und nicht garantiert wird, dass dieses Ergebnis innerhalb von 1 Sekunde verfügbar ist? Dann startet Ihre nächste Iteration ohne das erforderliche Ergebnis - da sie genau 1 Sekunde nach der vorherigen Iteration startet, ohne darauf zu achten, ob der von der vorherigen Iteration aufgerufene Rückruf tatsächlich abgeschlossen wurde oder nicht.
Treecoder
1

Bitte beachten Sie das Deasync- Modul. Ich persönlich mag die Promise-Methode nicht, um alle Funktionen asynchron zu machen, und das Schlüsselwort async / warte auf irgendetwas. Und ich denke, die offizielle node.js sollte in Betracht ziehen, die Event-Loop-API verfügbar zu machen, dies wird die Callback-Hölle einfach lösen. Node.js ist ein Framework, keine Sprache.

var node = require("deasync");
node.loop = node.runLoopOnce;

var done = 0;
// async call here
db.query("select * from ticket", (error, results, fields)=>{
    done = 1;
});

while (!done)
    node.loop();

// Now, here you go
Sunding Wei
quelle
0

Bei der Arbeit mit asynchronen Funktionen oder Observablen, die von Bibliotheken von Drittanbietern bereitgestellt werden, z. B. Cloud Firestore, habe ich festgestellt, dass Funktionen der waitForunten gezeigten Methode (TypeScript, aber Sie haben die Idee ...) hilfreich sind, wenn Sie auf einen Prozess warten müssen zu vervollständigen, aber Sie möchten keine Rückrufe in Rückrufe in Rückrufe einbetten müssen oder eine Endlosschleife riskieren.

Diese Methode ähnelt einer while (!condition)Schlafschleife, liefert jedoch asynchron und führt in regelmäßigen Abständen einen Test der Abschlussbedingung durch, bis true oder timeout ausgeführt wird.

export const sleep = (ms: number) => {
    return new Promise(resolve => setTimeout(resolve, ms))
}
/**
 * Wait until the condition tested in a function returns true, or until 
 * a timeout is exceeded.
 * @param interval The frenequency with which the boolean function contained in condition is called.
 * @param timeout  The maximum time to allow for booleanFunction to return true
 * @param booleanFunction:  A completion function to evaluate after each interval. waitFor will return true as soon as the completion function returns true.   
 */
export const waitFor = async function (interval: number, timeout: number,
    booleanFunction: Function): Promise<boolean> {
    let elapsed = 1;
    if (booleanFunction()) return true;
    while (elapsed < timeout) {
        elapsed += interval;
        await sleep(interval);
        if (booleanFunction()) {
            return true;
        }
    }
    return false;
}

Angenommen, Sie haben einen lang laufenden Prozess in Ihrem Backend, den Sie abschließen möchten, bevor eine andere Aufgabe ausgeführt wird. Wenn Sie beispielsweise eine Funktion haben, die eine Liste von Konten zusammenfasst, die Konten jedoch vor der Berechnung aus dem Backend aktualisieren möchten, können Sie Folgendes tun:

async recalcAccountTotals() : number {
     this.accountService.refresh();   //start the async process.
     if (this.accounts.dirty) {
           let updateResult = await waitFor(100,2000,()=> {return !(this.accounts.dirty)})
     }
 if(!updateResult) { 
      console.error("Account refresh timed out, recalc aborted");
      return NaN;
    }
 return ... //calculate the account total. 
}
user1023110
quelle