385 Stimmen

SetImmediate vs. nextTick

Die Version 0.10 von Node.js wurde heute veröffentlicht und führte setImmediate ein. Die API-Änderungen Dokumentation schlägt vor, es bei rekursiven nextTick Aufrufen zu verwenden.

Laut dem, was MDN sagt, scheint es sehr ähnlich zu process.nextTick zu sein.

Wann sollte ich nextTick und wann sollte ich setImmediate verwenden?

563voto

JohnnyHK Punkte 289697

Verwenden Sie setImmediate, wenn Sie die Funktion in die Warteschlange stellen möchten, hinter den I/O-Ereignisrückrufen, die bereits in der Ereignisschlange sind. Verwenden Sie process.nextTick, um die Funktion effektiv an den Anfang der Ereignisschlange zu stellen, damit sie sofort nach Abschluss der aktuellen Funktion ausgeführt wird.

Also in einem Fall, in dem Sie versuchen, einen lang laufenden, CPU-lastigen Job mit Rekursion zu unterbrechen, sollten Sie nun setImmediate anstelle von process.nextTick verwenden, um die nächste Iteration in die Warteschlange zu stellen, da ansonsten keine I/O-Ereignisrückrufe zwischen den Iterationen ausgeführt würden.

104voto

DraganS Punkte 2501

Als Beispiel:

import fs from 'fs';
import http from 'http';

const options = {
  host: 'www.stackoverflow.com',
  port: 80,
  path: '/index.html'
};

describe('deferredExecution', () => {
  it('deferredExecution', (done) => {
    console.log('Start');
    setTimeout(() => console.log('setTimeout 1'), 0);
    setImmediate(() => console.log('setImmediate 1'));
    process.nextTick(() => console.log('nextTick 1'));
    setImmediate(() => console.log('setImmediate 2'));
    process.nextTick(() => console.log('nextTick 2'));
    http.get(options, () => console.log('network IO'));
    fs.readdir(process.cwd(), () => console.log('file system IO 1'));
    setImmediate(() => console.log('setImmediate 3'));
    process.nextTick(() => console.log('nextTick 3'));
    setImmediate(() => console.log('setImmediate 4'));
    fs.readdir(process.cwd(), () => console.log('file system IO 2'));
    console.log('End');
    setTimeout(done, 1500);
  });
});

ergibt folgende Ausgabe

Start // synchron
End // synchron
nextTick 1 // microtask
nextTick 2 // microtask
nextTick 3 // microtask
setTimeout 1 // macrotask
file system IO 1 // macrotask
file system IO 2 // macrotask
setImmediate 1 // macrotask
setImmediate 2 // macrotask
setImmediate 3 // macrotask
setImmediate 4 // macrotask
network IO // macrotask

Ich hoffe, dass dies hilft, den Unterschied zu verstehen.

Aktualisiert:

Callbacks, die mit process.nextTick() verzögert werden, werden ausgeführt, bevor ein anderes I/O-Ereignis ausgelöst wird, während dies bei setImmediate() hinter jedem bereits in der Warteschlange befindlichen I/O-Ereignis erfolgt.

Node.js Design Patterns, von Mario Casciaro (vermutlich das beste Buch über Node.js/JS)

73voto

Chev Punkte 56446

Ich denke, ich kann dies recht gut veranschaulichen. Da nextTick am Ende des aktuellen Vorgangs aufgerufen wird, kann ein rekursiver Aufruf dazu führen, dass die Ereignisschleife blockiert wird. setImmediate löst dieses Problem, indem es in der Überprüfungsphase der Ereignisschleife feuert, wodurch die Ereignisschleife normal fortgesetzt werden kann.

>        timers        

       I/O-Rückrufe     

       Leerlauf, Vorbereitung     

           eingehend:   
           Umfrage          <  Verbindungen, 
           Daten usw.  

          Überprüfung          

    Schließen Rückrufe    

Quelle: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/

Beachten Sie, dass die Überprüfungsphase unmittelbar nach der Umfragephase liegt. Dies liegt daran, dass die Umfragephase und die I/O-Rückrufe die wahrscheinlichsten Stellen sind, an denen Ihre Aufrufe von setImmediate ausgeführt werden. Idealerweise werden die meisten dieser Aufrufe tatsächlich ziemlich unmittelbar sein, nur nicht so unmittelbar wie nextTick, der nach jedem Vorgang überprüft wird und technisch gesehen außerhalb der Ereignisschleife existiert.

Werfen wir einen Blick auf ein kleines Beispiel für den Unterschied zwischen setImmediate und process.nextTick:

function Schritt(Iteration) {
  if (Iteration === 10) return;
  setImmediate(() => {
    console.log(`setImmediate Iteration: ${Iteration}`);
    Schritt(Iteration + 1); // Rekursiver Aufruf vom setImmediate-Handler.
  });
  process.nextTick(() => {
    console.log(`nextTick Iteration: ${Iteration}`);
  });
}
Schritt(0);

Angenommen, wir haben dieses Programm gerade ausgeführt und befassen uns mit der ersten Iteration der Ereignisschleife. Es ruft die Funktion Schritt mit Iteration null auf. Es registriert dann zwei Handler, einen für setImmediate und einen für process.nextTick. Wir rufen diese Funktion dann rekursiv vom setImmediate-Handler auf, der in der nächsten Überprüfungsphase ausgeführt wird. Der nextTick-Handler wird am Ende des aktuellen Vorgangs ausgeführt, unterbricht die Ereignisschleife und wird daher, obwohl er als zweites registriert wurde, tatsächlich als Erstes ausgeführt.

Die Reihenfolge lautet wie folgt: nextTick wird am Ende des aktuellen Betriebs ausgelöst, die nächste Ereignisschleife beginnt, die normalen Phasen der Ereignisschleife werden ausgeführt, setImmediate wird ausgelöst und ruft rekursiv unsere Schritt-Funktion auf, um den Vorgang von vorne zu beginnen. Der aktuelle Betrieb endet, nextTick wird ausgelöst usw.

Die Ausgabe des obigen Codes wäre:

nextTick Iteration: 0
setImmediate Iteration: 0
nextTick Iteration: 1
setImmediate Iteration: 1
nextTick Iteration: 2
setImmediate Iteration: 2
nextTick Iteration: 3
setImmediate Iteration: 3
nextTick Iteration: 4
setImmediate Iteration: 4
nextTick Iteration: 5
setImmediate Iteration: 5
nextTick Iteration: 6
setImmediate Iteration: 6
nextTick Iteration: 7
setImmediate Iteration: 7
nextTick Iteration: 8
setImmediate Iteration: 8
nextTick Iteration: 9
setImmediate Iteration: 9

Lassen Sie uns nun unseren rekursiven Aufruf von Schritt in unseren nextTick-Handler verlegen, anstatt im setImmediate zu sein.

function Schritt(Iteration) {
  if (Iteration === 10) return;
  setImmediate(() => {
    console.log(`setImmediate Iteration: ${Iteration}`);
  });
  process.nextTick(() => {
    console.log(`nextTick Iteration: ${Iteration}`);
    Schritt(Iteration + 1); // Rekursiver Aufruf vom nextTick-Handler.
  });
}
Schritt(0);

Nachdem wir den rekursiven Aufruf von Schritt in den nextTick-Handler verschoben haben, wird sich das Verhalten in einer anderen Reihenfolge verhalten. Unsere erste Iteration der Ereignisschleife läuft und ruft Schritt auf, wobei ein setImmediate-Handler sowie ein nextTick-Handler registriert werden. Nachdem der aktuelle Vorgang beendet ist, wird unser nextTick-Handler ausgelöst, der rekursiv Schritt aufruft und einen weiteren setImmediate-Handler sowie einen weiteren nextTick-Handler registriert. Da ein nextTick-Handler nach dem aktuellen Betrieb ausgelöst wird, wird durch das Registrieren eines nextTick-Handlers innerhalb eines nextTick-Handlers der zweite Handler sofort nach Beendigung des aktuellen Handler-Betriebs ausgeführt. Die nextTick-Handler werden weiterhin ausgelöst, wodurch die aktuelle Ereignisschleife daran gehindert wird, fortzufahren. Bevor ein einziger setImmediate-Handler ausgelöst wird, werden alle unsere nextTick-Handler durchgeführt.

Die Ausgabe des obigen Codes lautet wie folgt:

nextTick Iteration: 0
nextTick Iteration: 1
nextTick Iteration: 2
nextTick Iteration: 3
nextTick Iteration: 4
nextTick Iteration: 5
nextTick Iteration: 6
nextTick Iteration: 7
nextTick Iteration: 8
nextTick Iteration: 9
setImmediate Iteration: 0
setImmediate Iteration: 1
setImmediate Iteration: 2
setImmediate Iteration: 3
setImmediate Iteration: 4
setImmediate Iteration: 5
setImmediate Iteration: 6
setImmediate Iteration: 7
setImmediate Iteration: 8
setImmediate Iteration: 9

Es sei darauf hingewiesen, dass, wenn wir den rekursiven Aufruf nicht unterbrechen und ihn nach 10 Iterationen abbrechen, die nextTick-Aufrufe weiterhin rekursiv ausgeführt werden und die Ereignisschleife niemals zur nächsten Phase übergehen lässt. So kann sich nextTick, wenn es rekursiv verwendet wird, blockieren, während setImmediate in der nächsten Ereignisschleife ausgelöst wird und das Einrichten eines weiteren setImmediate-Handlers innerhalb eines solchen die aktuelle Ereignisschleife überhaupt nicht unterbricht, was es ermöglicht, dass sie die Phasen der Ereignisschleife normal ausführt.

PS - Ich stimme anderen Kommentatoren zu, dass die Namen der beiden Funktionen leicht ausgetauscht werden könnten, da nextTick klingt, als würde es in der nächsten Ereignisschleife ausgelöst werden, anstatt am Ende der aktuellen, und das Ende der aktuellen Schleife ist "unmittelbarer" als der Beginn der nächsten Schleife. Na ja, das ist das, was wir bekommen, wenn eine API reift und die Menschen sich auf bestehende Schnittstellen verlassen.

31voto

Jay Day Zee Punkte 575

In den Kommentaren in der Antwort wird nicht explizit darauf hingewiesen, dass nextTick von der Makrosemantik zur Mikrosemantik gewechselt ist.

Vor Node 0.9 (als setImmediate eingeführt wurde) wurde nextTick am Anfang des nächsten Callstacks ausgeführt.

Seit Node 0.9 wird nextTick am Ende des bestehenden Callstacks ausgeführt, während setImmediate am Anfang des nächsten Callstacks ausgeführt wird.

Überprüfen Sie https://github.com/YuzuJS/setImmediate für Tools und Details

15voto

Brian Adams Punkte 42315

Einige großartige Antworten hier, die detailliert erklären, wie sie beide funktionieren.

Hier ist zusätzlich eine Antwort, die die spezifische gestellte Frage beantwortet:

Wann sollte ich nextTick verwenden und wann sollte ich setImmediate verwenden?


Verwenden Sie immer setImmediate.


Das Node.js Event Loop, Timers und process.nextTick() Dokument enthält Folgendes:

Wir empfehlen Entwicklern, in allen Fällen setImmediate() zu verwenden, da es einfacher zu verstehen ist (und zu Code führt, der mit einer größeren Vielzahl von Umgebungen kompatibel ist, wie z.B. Browser-JS.)


Früher im Dokument wird davor gewarnt, dass process.nextTick zu...

einigen schlechten Situationen führen kann, weil Sie Ihre E/A "aushungern lassen können, indem Sie rekursive process.nextTick() Aufrufe tätigen, was verhindert, dass der Ereignisschleifen-Zyklus die Poll-Phase erreicht.

Wie sich herausstellt, kann sogar process.nextTick Promises "aushungern":

Promise.resolve().then(() => { console.log('das passiert ZULETZT'); });

process.nextTick(() => {
  console.log('alle diese...');
  process.nextTick(() => {
    console.log('...geschehen vor...');
    process.nextTick(() => {
      console.log('...das Versprechen je...');
      process.nextTick(() => {
        console.log('...eine Chance hat, sich aufzulösen');
      })
    })
  })
})

Andererseits ist setImmediate "einfacher zu verstehen" und vermeidet diese Arten von Problemen:

Promise.resolve().then(() => { console.log('das passiert ZUERST'); });

setImmediate(() => {
  console.log('das passiert ZULETZT');
})

Also, es sei denn, es besteht ein spezifischer Bedarf an dem einzigartigen Verhalten von process.nextTick, ist der empfohlene Ansatz "in allen Fällen setImmediate() zu verwenden".

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X