setImmediate vs. nextTick


336

La versione 0.10 di Node.js è stata rilasciata oggi e introdotta setImmediate. La documentazione relativa alle modifiche dell'API suggerisce di utilizzarla quando si effettuano nextTickchiamate ricorsive .

Da quello che dice MDN sembra molto simile a process.nextTick.

Quando dovrei usare nextTicke quando dovrei usare setImmediate?


20
Ci sono 5 paragrafi su questa modifica nel blog blog.nodejs.org/2013/03/11/node-v0-10-0-stable
mak

1
Dai benchmark delle prestazioni sembra che nextTicksia più veloce rispetto setImmediateai calcoli di grandi dimensioni.

10
Per la cronaca, ho letto prima quei cinque paragrafi e ho ancora finito con questa domanda quando non mi ha chiarito nulla. La risposta accettata è molto più concisa e in realtà descrive cosa setImmediatefa meglio.
Chev,

Ho spiegato la differenza in modo molto dettagliato nel mio blog .
plafer

È possibile che GC possa essere eseguito prima setImmediate, ma non prima nextTick?

Risposte:


511

Utilizzare setImmediatese si desidera mettere in coda la funzione dietro qualsiasi callback di eventi I / O che sono già nella coda degli eventi. Utilizzare process.nextTickper accodare efficacemente la funzione in testa alla coda degli eventi in modo che venga eseguita immediatamente dopo il completamento della funzione corrente.

Quindi, nel caso in cui si stia tentando di interrompere un processo in esecuzione a lungo, associato alla CPU mediante la ricorsione, ora si vorrebbe utilizzare setImmediatepiuttosto che process.nextTickmettere in coda la successiva iterazione poiché altrimenti eventuali callback di eventi I / O non avrebbero la possibilità correre tra le iterazioni.


86
I callback passati a process.nextTick di solito vengono chiamati alla fine del flusso corrente di esecuzione e sono quindi approssimativamente veloci quanto la chiamata di una funzione in modo sincrono. Se lasciato deselezionato, ciò affiderebbe il loop degli eventi, evitando che si verifichino eventuali I / O. setImmediates viene messo in coda nell'ordine creato e viene rimosso dalla coda una volta per ogni iterazione di loop. Ciò è diverso da process.nextTick che eseguirà i callback in coda process.maxTickDepth per iterazione. setImmediate cederà al loop degli eventi dopo aver attivato un callback in coda per assicurarsi che l'I / O non sia affamato.
Benjamin Gruenbaum,

2
@UstamanSangat setImmediate è supportato solo da IE10 +, tutti gli altri browser si stanno ostinatamente rifiutando di implementare il probabile standard futuro perché non amano essere battuti da Microsoft. Per ottenere un risultato simile in FF / Chrome, è possibile utilizzare postMessage (pubblicare un messaggio nella propria finestra). Puoi anche prendere in considerazione l'utilizzo di requestAnimationFrame, soprattutto se i tuoi aggiornamenti sono correlati all'interfaccia utente. setTimeout (func, 0) non funziona come process.nextTick.
fabspro,

45
@fabspro "perché a loro non piace essere battuti dal mio Microsoft" ti fa sembrare dolorante per qualcosa. È principalmente perché è terribilmente, terribilmente chiamato. Se c'è una volta la funzione SetImmediate non verrà mai, mai eseguita, è immediatamente. Il nome della funzione è esattamente l'opposto di ciò che fa. nextTick e setImmediate sarebbe meglio cambiare; setImmediate viene eseguito immediatamente dopo il completamento dello stack corrente (prima di attendere l'I / O) e nextTick viene eseguito alla fine del segno di spunta successivo (dopo aver atteso l'I / O). Ma poi, questo è già stato detto mille volte.
Craig Andrews,

4
@fabspro Ma sfortunatamente la funzione si chiama nextTick. nextTick esegue "immediatamente" mentre setImmediate è più simile a setTimeout / postMessage.
Robert,

1
@CraigAndrews Eviterei requestAnimationFrameperché non sempre si verifica (l'ho visto sicuramente, penso che l'esempio fosse la scheda non era la scheda corrente) e può essere chiamato prima che la pagina abbia completato il disegno (cioè il browser è ancora occupato a disegnare).
robocat,

68

Come un'illustrazione

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);
  });
});

darà il seguente output

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

Spero che questo possa aiutare a capire la differenza.

aggiornato:

I callback differiti con process.nextTick()esecuzione prima che venga attivato qualsiasi altro evento I / O, mentre con setImmediate (), l'esecuzione viene messa in coda dietro a qualsiasi evento I / O già presente nella coda.

Node.js Design Patterns , di Mario Casciaro (probabilmente il miglior libro su node.js / js)


2
Questo è davvero utile, grazie. Penso che immagini ed esempi siano il modo più veloce per capire qualcosa.
John James

1
Penso che sia importante sottolineare che setTimeout () e setImmediate () quando non all'interno di un ciclo I / O l'ordine è non deterministico a seconda delle prestazioni di un processo. 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: Quindi, questa risposta non risponde davvero alla differenza esatta, ma semplicemente un esempio che potrebbe variare in contesti diversi
Actung

Come sottolineato da @Actung. è molto importante sapere se setTimetout e setImmediate sono all'interno di un ciclo I / O o no, per determinare il risultato.
Rajika Imal,

50

Penso di poterlo illustrare abbastanza bene. Poiché nextTickviene chiamato al termine dell'operazione corrente, chiamarlo in modo ricorsivo può finire per impedire al loop degli eventi di continuare. setImmediaterisolve questo problema attivando la fase di controllo del loop degli eventi, consentendo al loop degli eventi di continuare normalmente.

   ┌───────────────────────┐
┌─>│        timers         
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       I/O callbacks     
  └──────────┬────────────┘
  ┌──────────┴────────────┐
       idle, prepare     
  └──────────┬────────────┘      ┌───────────────┐
  ┌──────────┴────────────┐         incoming:   
           poll          │<─────┤  connections, 
  └──────────┬────────────┘         data, etc.  
  ┌──────────┴────────────┐      └───────────────┘
          check          
  └──────────┬────────────┘
  ┌──────────┴────────────┐
└──┤    close callbacks    
   └───────────────────────┘

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

Si noti che la fase di controllo è immediatamente dopo la fase di polling. Questo perché la fase di polling e i callback I / O sono i luoghi più probabili in cui setImmediateverranno eseguite le chiamate . Quindi, idealmente, la maggior parte di quelle chiamate sarà in realtà abbastanza immediata, non altrettanto immediata di quella nextTickche viene controllata dopo ogni operazione ed esiste tecnicamente al di fuori del ciclo degli eventi.

Diamo un'occhiata a un piccolo esempio della differenza tra setImmediatee 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);

Diciamo che abbiamo appena eseguito questo programma e stiamo attraversando la prima iterazione del loop degli eventi. Chiamerà nella stepfunzione con iterazione zero. Quindi registrerà due gestori, uno per setImmediatee uno per process.nextTick. Chiamiamo quindi ricorsivamente questa funzione dal setImmediategestore che verrà eseguito nella successiva fase di controllo. Il nextTickgestore verrà eseguito al termine dell'operazione corrente interrompendo il ciclo degli eventi, quindi anche se è stato registrato per secondo, verrà effettivamente eseguito per primo.

L'ordine finisce per essere: si nextTickaccende al termine dell'operazione corrente, inizia il ciclo dell'evento successivo, si eseguono le normali fasi del ciclo di eventi, si setImmediateattiva e chiama ricorsivamente la nostra stepfunzione per ricominciare il processo. L'operazione corrente termina, nextTickincendi, ecc.

L'output del codice sopra sarebbe:

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

Ora spostiamo la nostra chiamata ricorsiva stepnel nostro nextTickgestore anziché in 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);

Ora che abbiamo spostato la chiamata ricorsiva stepnel nextTickgestore, le cose si comporteranno in un ordine diverso. La nostra prima iterazione del ciclo di eventi viene eseguita e chiama la stepregistrazione di un setImmedaitegestore e di un nextTickgestore. Al termine dell'operazione corrente, il nostro nextTickgestore emette un incendio che chiama in modo ricorsivo stepe registra un altro setImmediategestore e un altro nextTickgestore. Poiché un nextTickgestore si attiva dopo l'operazione corrente, la registrazione di un nextTickgestore all'interno di un nextTickgestore causerà l'esecuzione del secondo gestore immediatamente al termine dell'operazione del gestore corrente. I nextTickgestori continueranno a sparare, impedendo che l'attuale loop degli eventi continui. Passeremo attraverso tutto il nostronextTickgestori prima di vedere un singolo setImmediateincendio gestore.

L'output del codice precedente finisce per essere:

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

Si noti che se non avessimo interrotto la chiamata ricorsiva e l'abbia interrotta dopo 10 iterazioni, le nextTickchiamate continuerebbero a ripetersi e non permetterebbero che il ciclo di eventi continui alla fase successiva. Questo è il modo in cui nextTickpuò diventare bloccante se usato in modo ricorsivo mentre si setImmediateattiverà nel successivo ciclo di eventi e l'impostazione di un altro setImmediategestore all'interno di uno non interromperà affatto il ciclo di eventi corrente, consentendogli di continuare a eseguire normalmente le fasi del ciclo di eventi.

Spero che aiuti!

PS: concordo con gli altri commentatori sul fatto che i nomi delle due funzioni potrebbero essere facilmente scambiati poiché nextTicksembra che si spari nel prossimo loop dell'evento piuttosto che alla fine di quello corrente, e la fine del loop corrente è più "immediata" "rispetto all'inizio del ciclo successivo. Oh bene, questo è ciò che otteniamo quando un'API matura e le persone dipendono dalle interfacce esistenti.


2
Descrizione abbastanza chiara. Penso che questa risposta abbia bisogno di più voti.
Actung

Ben spiegato (Y)
Dhiraj Sharma,

è importante ribadire l'avvertimento di Node sull'uso di process.nextTick. Se accodate un gran numero di callback in nextTickQueue, potete potenzialmente morire di fame il loop degli eventi assicurandovi che la fase di polling non venga mai raggiunta. Questo è il motivo per cui generalmente dovresti preferire setImmediate.
Faridcs,

1
Grazie, questa è stata la migliore spiegazione. il codice di esempio ha davvero aiutato.
skyhavoc,

@skyhavoc felice di poterti aiutare!
Chev,

30

Nei commenti nella risposta, non si afferma esplicitamente che nextTick sia passato da Macrosemantics a Microsemantics.

prima del nodo 0.9 (quando è stato introdotto setImmediate), nextTick ha funzionato all'inizio del callstack successivo.

dal nodo 0.9, nextTick funziona alla fine del callstack esistente, mentre setImmediate è all'inizio del callstack successivo

controlla https://github.com/YuzuJS/setImmediate per strumenti e dettagli


11

In termini semplici, process.NextTick () verrebbe eseguito al prossimo tick del loop degli eventi. Tuttavia, setImmediate ha sostanzialmente una fase separata che assicura che il callback registrato in setImmediate () venga chiamato solo dopo la callback IO e la fase di polling.

Si prega di fare riferimento a questo link per una bella spiegazione: https://medium.com/the-node-js-collection/what-you-should-know-to-really-understand-the-node-js-event-loop-and -La sua-metrics-c4907b19da4c

eventi di loop di eventi semplificati


8

Alcune grandi risposte qui che descrivono in dettaglio come funzionano entrambe.

Basta aggiungerne uno che risponda alla domanda specifica posta:

Quando dovrei usare nextTicke quando dovrei usare setImmediate?


Usa sempre setImmediate.


Il ciclo eventi, i timer e ilprocess.nextTick() documento Node.js includono quanto segue:

Raccomandiamo agli sviluppatori di utilizzare setImmediate()in ogni caso perché è più facile ragionare (e porta a un codice compatibile con una più ampia varietà di ambienti, come il browser JS.)


In precedenza nel documento avverte che process.nextTickpuò portare a ...

alcune brutte situazioni perché ti permette di "morire di fame" il tuo I / O effettuando process.nextTick()chiamate ricorsive , il che impedisce al loop di eventi di raggiungere la fase di polling .

A quanto pare, process.nextTickpuò persino morire di fame 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');
      })
    })
  })
})

D'altra parte, setImmediateè " più facile ragionare su " ed evita questi tipi di problemi:

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

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

Quindi, a meno che non vi sia una necessità specifica per il comportamento unico di process.nextTick, l'approccio raccomandato è di " utilizzare setImmediate()in tutti i casi ".


1

Ti consiglio di consultare la sezione documenti dedicata a Loop per una migliore comprensione. Alcuni frammenti presi da lì:

Abbiamo due chiamate simili per quanto riguarda gli utenti, ma i loro nomi sono confusi.

  • process.nextTick () si attiva immediatamente nella stessa fase

  • setImmediate () si attiva sulla seguente iterazione o "tick" del
    loop degli eventi

In sostanza, i nomi dovrebbero essere scambiati. process.nextTick () si attiva più immediatamente di setImmediate (), ma questo è un artefatto del passato che è improbabile che cambi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.