Myślę, że mogę to całkiem dobrze zilustrować. Ponieważ nextTick
jest wywoływany pod koniec bieżącej operacji, wywołanie go rekurencyjnie może w rezultacie zablokować kontynuowanie pętli zdarzeń. setImmediate
rozwią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 setImmediate
których będą uruchamiane połączenia. Idealnie więc większość z tych wywołań będzie faktycznie natychmiastowa, ale nie tak natychmiastowa, jak nextTick
jest sprawdzana po każdej operacji i technicznie istnieje poza pętlą zdarzeń.
Rzućmy okiem na mały przykład różnicy między setImmediate
i 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 step
funkcję z iteracją zero. Następnie zarejestruje dwa moduły obsługi, jeden dla setImmediate
i jeden dla process.nextTick
. Następnie rekurencyjnie wywołujemy tę funkcję z modułu setImmediate
obsługi, który będzie działał w następnej fazie sprawdzania. Procedura nextTick
obsł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: nextTick
uruchamia się po zakończeniu bieżącej operacji, rozpoczyna się kolejna pętla zdarzeń, wykonywane są normalne fazy pętli zdarzeń, setImmediate
odpala i rekurencyjnie wywołuje naszą step
funkcję, aby rozpocząć proces od nowa. Bieżąca operacja kończy się, nextTick
poż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 step
naszego nextTick
moduł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 step
modułu nextTick
obsługi wszystko będzie się zachowywać w innej kolejności. Nasza pierwsza iteracja pętli zdarzeń działa i wywołuje step
rejestrację modułu setImmedaite
obsługi i modułu nextTick
obsługi. Po zakończeniu bieżącej operacji nasz nextTick
moduł obsługi odpala, który rekurencyjnie wywołuje step
i rejestruje inny setImmediate
moduł obsługi, a także inny nextTick
moduł obsługi. Ponieważ program nextTick
obsługi uruchamia się po bieżącej operacji, zarejestrowanie programu nextTick
obsługi w nextTick
module obsługi spowoduje, że drugi moduł obsługi zostanie uruchomiony natychmiast po zakończeniu bieżącej operacji modułu obsługi. Procedury nextTick
obsługi będą nadal odpalać, zapobiegając kontynuacji bieżącej pętli zdarzeń. Przejdziemy przez wszystkie naszenextTick
setImmediate
handleró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, nextTick
połączenia będą się powtarzać i nigdy nie pozwolą pętli zdarzeń przejść do następnej fazy. W ten sposób nextTick
może zostać zablokowany, gdy zostanie użyty rekurencyjnie, podczas gdy setImmediate
będzie uruchamiany w następnej pętli zdarzeń, a ustawienie innego modułu setImmediate
obsł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ż nextTick
brzmi 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.