setImmediate vs. nextTick


336

Wersja 0.10 Node.js została wydana dzisiaj i wprowadzona setImmediate. Dokumentacja zmian interfejsu API sugeruje używanie go podczas wykonywania nextTickpołączeń rekurencyjnych .

Z tego, co mówi MDN , wydaje się bardzo podobne process.nextTick.

Kiedy powinienem nextTicki kiedy powinienem setImmediate?


20
Istnieje 5 akapitów na temat tej zmiany na blogu blog.nodejs.org/2013/03/11/node-v0-10-0-stable
mak

1
Z testów wydajności wynika, że nextTickjest szybszy niż setImmediateprzy dużych obliczeniach.

10
Dla przypomnienia, najpierw przeczytałem te pięć akapitów i nadal skończyłem na tym pytaniu, kiedy tak naprawdę nic mi nie wyjaśniło. Przyjęta odpowiedź jest znacznie bardziej zwięzła i faktycznie opisuje to, co setImmediaterobi bardziej szczegółowo.
Chev

Różnicę wyjaśniłem bardzo szczegółowo na moim blogu .
plafer

Czy GC może działać wcześniej setImmediate, ale nie wcześniej nextTick?

Odpowiedzi:


510

Użyj, setImmediatejeśli chcesz ustawić w kolejce funkcję za wywołaniami zwrotnymi zdarzeń we / wy, które już znajdują się w kolejce zdarzeń. Służy process.nextTickdo skutecznego umieszczania w kolejce funkcji na początku kolejki zdarzeń, aby działała natychmiast po zakończeniu bieżącej funkcji.

Tak więc w przypadku, gdy próbujesz przerwać długotrwałe, związane z procesorem zadanie za pomocą rekurencji, wolisz teraz użyć setImmediatezamiast process.nextTickkolejkować kolejną iterację, ponieważ w przeciwnym razie wywołania zwrotne zdarzeń we / wy nie miałyby szansy biegać między iteracjami.


86
Oddzwanianie przekazywane do process.nextTick będzie zwykle wywoływane pod koniec bieżącego przepływu wykonania, a zatem są w przybliżeniu tak szybkie, jak wywoływanie funkcji synchronicznie. Pozostawione niezaznaczone, spowoduje to zagłębienie pętli zdarzeń, zapobiegając wystąpieniu jakiegokolwiek wejścia / wyjścia. setImmediates są ustawiane w kolejce w utworzonej kolejności i są usuwane z kolejki raz na iterację w pętli. Różni się to od process.nextTick, który wykona wywołania kolejkowe process.maxTickDepth w każdej iteracji. setImmediate ustąpi pętli zdarzeń po uruchomieniu wywołania zwrotnego w kolejce, aby upewnić się, że I / O nie jest głodzony.
Benjamin Gruenbaum,

2
@UstamanSangat setImmediate jest obsługiwany tylko przez IE10 +, wszystkie inne przeglądarki uparcie odmawiają wdrożenia prawdopodobnego przyszłego standardu, ponieważ nie lubią być bite przez Microsoft. Aby osiągnąć podobny wynik w FF / Chrome, możesz użyć postMessage (opublikuj wiadomość we własnym oknie). Możesz również rozważyć użycie requestAnimationFrame, szczególnie jeśli twoje aktualizacje są związane z interfejsem użytkownika. setTimeout (func, 0) w ogóle nie działa jak process.nextTick.
fabspro

45
@fabspro ", ponieważ nie lubią bicia mojego Microsoftu", sprawiasz, że coś cię boli. To głównie dlatego, że jest strasznie, strasznie nazwane. Jeśli jest ustawiony jeden raz Funkcja natychmiastowa nigdy nie uruchomi się, jest natychmiast. Nazwa funkcji jest dokładnym przeciwieństwem tego, co robi. nextTick i setImmediate lepiej byłoby przełączać się; setImmediate wykonuje się natychmiast po zakończeniu bieżącego stosu (przed oczekiwaniem we / wy), a nextTick wykonuje się na końcu następnego tiku (po oczekiwaniu we / wy). Ale powiedziano to już tysiąc razy.
Craig Andrews

4
@fabspro Ale niestety funkcja nazywa się nextTick. nextTick wykonuje się „natychmiast”, podczas gdy setImmediate przypomina bardziej setTimeout / postMessage.
Robert,

1
@CraigAndrews Unikałbym, requestAnimationFrameponieważ nie zawsze tak się dzieje (na pewno to widziałem, myślę, że przykład była tabulacją, a nie obecną tabulatorem) i można ją wywołać przed zakończeniem malowania strony (tzn. Przeglądarka jest nadal zajęta rysowaniem).
robocat

68

Jako ilustracja

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('TO1'), 0);
    setImmediate(() => console.log('IM1'));
    process.nextTick(() => console.log('NT1'));
    setImmediate(() => console.log('IM2'));
    process.nextTick(() => console.log('NT2'));
    http.get(options, () => console.log('IO1'));
    fs.readdir(process.cwd(), () => console.log('IO2'));
    setImmediate(() => console.log('IM3'));
    process.nextTick(() => console.log('NT3'));
    setImmediate(() => console.log('IM4'));
    fs.readdir(process.cwd(), () => console.log('IO3'));
    console.log('Done');
    setTimeout(done, 1500);
  });
});

da następujący wynik

Start
Done
NT1
NT2
NT3
TO1
IO2
IO3
IM1
IM2
IM3
IM4
IO1

Mam nadzieję, że to pomoże zrozumieć różnicę.

Zaktualizowano:

Oddzwanianie odroczone przy process.nextTick()uruchamianiu przed uruchomieniem dowolnego innego zdarzenia We / Wy, natomiast w przypadku setImmediate () wykonanie jest ustawiane w kolejce za każdym zdarzeniem We / Wy, które już znajduje się w kolejce.

Wzory projektowe Node.js , Mario Casciaro (prawdopodobnie najlepsza książka o node.js / js)


2
To bardzo pomocne dzięki. Myślę, że obrazy i przykłady są najszybszym sposobem na zrozumienie czegoś.
John James

1
Myślę, że ważne jest, aby zaznaczyć, że setTimeout () i setImmediate (), gdy nie są w cyklu I / O, kolejność nie jest deterministyczna w zależności od wydajności procesu. nodejs.org/en/docs/guides/event-loop-timers-and-nexttick For example, if we run the following script which is not within an I/O cycle (i.e. the main module), the order in which the two timers are executed is non-deterministic, as it is bound by the performance of the process: Tak więc ta odpowiedź tak naprawdę nie odpowiada dokładnej różnicy, a jedynie przykład, który może się różnić w innym kontekście
Actung

Jak wskazał @Actung. bardzo ważne jest, aby wiedzieć, czy setTimetout i setImmediate mieszczą się w cyklu We / Wy, czy nie, w celu ustalenia wyniku.
Rajika Imal

50

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.


2
Całkiem jasny opis. Myślę, że ta odpowiedź wymaga więcej pozytywnych opinii.
Actung

Dobrze wyjaśnione (Y)
Dhiraj Sharma

ważne jest, aby powtórzyć ostrzeżenie Node dotyczące użycia process.nextTick. Jeśli w kolejnej funkcji ClickTickQueue zamienisz dużą liczbę wywołań zwrotnych, możesz potencjalnie zagłodzić pętlę zdarzeń, zapewniając, że faza odpytywania nigdy nie zostanie osiągnięta. To jest powód, dla którego powinieneś ogólnie preferować setImmediate.
faridcs

1
Dzięki, to było najlepsze wytłumaczenie. przykładowy kod naprawdę pomógł.
skyhavoc

@skyhavoc cieszę się, że mogłem pomóc!
Chev

30

W komentarzach w odpowiedzi nie stwierdza się wyraźnie, że nextTick przeszedł z makrosemantyki na mikrosemantykę.

przed węzłem 0.9 (kiedy wprowadzono setImmediate), nextTick działał na początku następnego stosu wywołań.

od węzła 0.9 nextTick działa na końcu istniejącego stosu wywołań, podczas gdy setImmediate jest na początku następnego stosu wywołań

sprawdź https://github.com/YuzuJS/setImmediate po narzędzia i szczegóły


11

Mówiąc najprościej, process.NextTick () byłby wykonywany przy następnym tiku pętli zdarzeń. Jednak setImmediate ma zasadniczo osobną fazę, która zapewnia, że ​​wywołanie zwrotne zarejestrowane w setImmediate () będzie wywoływane dopiero po fazie oddzwonienia i odpytywania We / Wy.

Proszę zapoznać się z tym linkiem, aby uzyskać ładne wyjaśnienie: https://medium.com/the-node-js-collection/what-you-should-know-to-really-understand-the-node-js-event-loop-and -its-metrics-c4907b19da4c

uproszczone zdarzenia pętli zdarzeń


8

Oto kilka świetnych odpowiedzi opisujących ich działanie.

Wystarczy dodać taką, która odpowiada na konkretne zadane pytanie:

Kiedy powinienem nextTicki kiedy powinienem setImmediate?


Zawsze używaj setImmediate.


Node.js pętli zdarzeń, timery, aprocess.nextTick() doc zawiera następujące elementy:

Zalecamy, aby programiści używali go setImmediate()we wszystkich przypadkach, ponieważ łatwiej jest o nim myśleć (i prowadzi to do kodu, który jest kompatybilny z wieloma różnymi środowiskami, takimi jak JS przeglądarki).


Wcześniej w dokumencie ostrzega, że process.nextTickmoże prowadzić do ...

niektóre złe sytuacje, ponieważ umożliwiają „głodzenie” wejścia / wyjścia przez wykonywanie process.nextTick()połączeń rekurencyjnych , co uniemożliwia pętli zdarzeń dotarcie do fazy odpytywania .

Jak się okazuje, process.nextTickmoże nawet głodować Promises:

Promise.resolve().then(() => { console.log('this happens LAST'); });

process.nextTick(() => {
  console.log('all of these...');
  process.nextTick(() => {
    console.log('...happen before...');
    process.nextTick(() => {
      console.log('...the Promise ever...');
      process.nextTick(() => {
        console.log('...has a chance to resolve');
      })
    })
  })
})

Z drugiej strony setImmediatejest „ łatwiejszy do uzasadnienia ” i pozwala uniknąć tego rodzaju problemów:

Promise.resolve().then(() => { console.log('this happens FIRST'); });

setImmediate(() => {
  console.log('this happens LAST');
})

Tak więc, chyba że istnieje szczególna potrzeba wyjątkowego zachowania process.nextTick, zalecanym podejściem jest „ używać setImmediate()we wszystkich przypadkach ”.


1

Polecam sprawdzić sekcję dokumentacji poświęconą Loop, aby lepiej zrozumieć. Niektóre fragmenty pobrane stamtąd:

Mamy dwa połączenia, które są podobne pod względem użytkowników, ale ich nazwy są mylące.

  • process.nextTick () odpala natychmiast na tej samej fazie

  • setImmediate () odpala w następnej iteracji lub „tik” w
    pętli zdarzeń

Zasadniczo nazwy powinny zostać zamienione. process.nextTick () odpala szybciej niż setImmediate (), ale jest to artefakt z przeszłości, którego zmiana jest mało prawdopodobna.

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.