Myślę, że mogę to całkiem dobrze zilustrować. Ponieważ nextTickjest wywoływany pod koniec bieżącej operacji, wywołanie go rekurencyjnie może w rezultacie zablokować kontynuowanie pętli zdarzeń. setImmediaterozwiązuje to, uruchamiając w fazie sprawdzania pętli zdarzeń, umożliwiając normalne kontynuowanie pętli zdarzeń.
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
źródło: https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
Zauważ, że faza sprawdzania następuje bezpośrednio po fazie odpytywania. Wynika to z faktu, że faza odpytywania i wywołania zwrotne we / wy są najbardziej prawdopodobnymi miejscami, w setImmediatektórych będą uruchamiane połączenia. Idealnie więc większość z tych wywołań będzie faktycznie natychmiastowa, ale nie tak natychmiastowa, jak nextTickjest sprawdzana po każdej operacji i technicznie istnieje poza pętlą zdarzeń.
Rzućmy okiem na mały przykład różnicy między setImmediatei process.nextTick:
function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
step(iteration + 1); // Recursive call from setImmediate handler.
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
});
}
step(0);
Powiedzmy, że właśnie uruchomiliśmy ten program i wykonujemy pierwszą iterację pętli zdarzeń. Wywoła stepfunkcję z iteracją zero. Następnie zarejestruje dwa moduły obsługi, jeden dla setImmediatei jeden dla process.nextTick. Następnie rekurencyjnie wywołujemy tę funkcję z modułu setImmediateobsługi, który będzie działał w następnej fazie sprawdzania. Procedura nextTickobsługi uruchomi się na końcu bieżącej operacji, przerywając pętlę zdarzeń, więc nawet jeśli została zarejestrowana jako druga, faktycznie uruchomi się jako pierwsza.
Kolejność jest następująca: nextTickuruchamia się po zakończeniu bieżącej operacji, rozpoczyna się kolejna pętla zdarzeń, wykonywane są normalne fazy pętli zdarzeń, setImmediateodpala i rekurencyjnie wywołuje naszą stepfunkcję, aby rozpocząć proces od nowa. Bieżąca operacja kończy się, nextTickpożary itp.
Dane wyjściowe powyższego kodu to:
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
Teraz przenieśmy nasze rekurencyjne wywołanie do stepnaszego nextTickmodułu obsługi zamiast setImmediate.
function step(iteration) {
if (iteration === 10) return;
setImmediate(() => {
console.log(`setImmediate iteration: ${iteration}`);
});
process.nextTick(() => {
console.log(`nextTick iteration: ${iteration}`);
step(iteration + 1); // Recursive call from nextTick handler.
});
}
step(0);
Po przeniesieniu wywołania rekurencyjnego do stepmodułu nextTickobsługi wszystko będzie się zachowywać w innej kolejności. Nasza pierwsza iteracja pętli zdarzeń działa i wywołuje steprejestrację modułu setImmedaiteobsługi i modułu nextTickobsługi. Po zakończeniu bieżącej operacji nasz nextTickmoduł obsługi odpala, który rekurencyjnie wywołuje stepi rejestruje inny setImmediatemoduł obsługi, a także inny nextTickmoduł obsługi. Ponieważ program nextTickobsługi uruchamia się po bieżącej operacji, zarejestrowanie programu nextTickobsługi w nextTickmodule obsługi spowoduje, że drugi moduł obsługi zostanie uruchomiony natychmiast po zakończeniu bieżącej operacji modułu obsługi. Procedury nextTickobsługi będą nadal odpalać, zapobiegając kontynuacji bieżącej pętli zdarzeń. Przejdziemy przez wszystkie naszenextTicksetImmediatehandlerów, zanim zobaczymy, że wystrzelił jednego handlera.
Wynikiem powyższego kodu jest:
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
Zauważ, że gdybyśmy nie przerwali połączenia rekurencyjnego i nie przerwali go po 10 iteracjach, nextTickpołączenia będą się powtarzać i nigdy nie pozwolą pętli zdarzeń przejść do następnej fazy. W ten sposób nextTickmoże zostać zablokowany, gdy zostanie użyty rekurencyjnie, podczas gdy setImmediatebędzie uruchamiany w następnej pętli zdarzeń, a ustawienie innego modułu setImmediateobsługi z jednego z nich w ogóle nie zakłóci bieżącej pętli zdarzeń, umożliwiając normalne kontynuowanie wykonywania faz pętli zdarzeń.
Mam nadzieję, że to pomaga!
PS - Zgadzam się z innymi komentatorami, że nazwy dwóch funkcji można łatwo zamienić, ponieważ nextTickbrzmi to tak, jakby zadziałało w następnej pętli zdarzeń, a nie na końcu bieżącej, a koniec bieżącej pętli jest bardziej „natychmiastowy” ”niż początek następnej pętli. No cóż, to właśnie dostajemy, gdy API dojrzewa i ludzie zaczynają polegać na istniejących interfejsach.