Differenza tra microtask e macrotask in un contesto di loop di eventi


140

Ho appena finito di leggere le specifiche Promises / A + e sono incappato nei termini microtask e macrotask: vedi http://promisesaplus.com/#notes

Non ho mai sentito parlare di questi termini prima, e ora sono curioso di sapere quale potrebbe essere la differenza?

Ho già provato a trovare alcune informazioni sul web, ma tutto ciò che ho trovato è questo post dagli Archivi di w3.org (che non mi spiega la differenza): http://lists.w3.org/Archives /Public/public-nextweb/2013Jul/0018.html

Inoltre, ho trovato un modulo npm chiamato "macrotask": https://www.npmjs.org/package/macrotask Ancora una volta, non è stato chiarito quale sia esattamente la differenza.

Tutto quello che so è che ha qualcosa a che fare con il loop degli eventi, come descritto in https://html.spec.whatwg.org/multipage/webappapis.html#task-queue e https: //html.spec.whatwg .org / multipagina / webappapis.html # eseguire-a-Microtask-checkpoint

So che teoricamente dovrei essere in grado di estrarre le differenze da solo, data questa specifica WHATWG. Ma sono sicuro che anche altri potrebbero trarre vantaggio da una breve spiegazione fornita da un esperto.


In breve: più code di eventi nidificate. Potresti persino implementarne uno tu stesso:while (task = todo.shift()) task();
Bergi,

1
Per qualcuno che vuole un po 'più di dettagli: Segreti di JavaScript Ninja, 2a edizione, CAPITOLO 13 Eventi sopravvissuti
Ethan,

Risposte:


220

Un giro del ciclo di eventi avrà esattamente un'attività che viene elaborata dalla coda macrotask (questa coda è semplicemente chiamata la coda delle attività nella specifica WHATWG ). Al termine di questo macrotask, tutti i microtask disponibili saranno elaborati, vale a dire all'interno dello stesso ciclo di rotazione . Mentre questi microtask vengono elaborati, possono mettere in coda ancora più microtask, che verranno eseguiti uno per uno fino a quando la coda di microtask non si esaurisce.

Quali sono le conseguenze pratiche di questo?

Se un microtask inserisce in modo ricorsivo altri microtask, potrebbe impiegare molto tempo prima che il macrotask successivo venga elaborato. Ciò significa che potresti finire con un'interfaccia utente bloccata o un I / O finito inattivo nella tua applicazione.

Tuttavia, almeno per quanto riguarda la funzione process.nextTick di Node.js (che mette in coda i microtask ), esiste una protezione integrata contro tale blocco mediante process.maxTickDepth. Questo valore è impostato su un valore predefinito di 1000, riducendo ulteriore lavorazione di microtasks dopo tale limite viene raggiunto, che permette il successivo macrotask da lavorare)

Quindi quando usare cosa?

Fondamentalmente, usa i microtask quando hai bisogno di fare cose in modo asincrono in modo sincrono (cioè quando diresti di eseguire questo (micro) compito nel futuro più immediato ). Altrimenti, attenersi ai macrotask .

Esempi

macrotask: setTimeout , setInterval , setImmediate , requestAnimationFrame , I / O ,
microtask di rendering dell'interfaccia utente : process.nextTick , Promises , queueMicrotask , MutationObserver


4
Sebbene ci sia un checkpoint microtask nel loop degli eventi, non è qui che la maggior parte degli sviluppatori incontrerà microtask. I microtask vengono elaborati quando si svuota lo stack JS. Ciò può accadere più volte all'interno di un'attività, o anche all'interno delle fasi di rendering del ciclo degli eventi.
JaffaTheCake,

2
process.maxTickDepthè stato rimosso molto tempo fa: github.com/nodejs/node/blob/…
RidgeA

puoi anche usare il metodo queueMicrotask () per aggiungere un nuovo microtask
ZoomTutto il

Grazie @ZoomAll, finora non conoscevo queueMicrotask (). L'ho aggiunto alla risposta più i collegamenti a tutte le cose ...
NicBright

requestAnimationFrame (rAF) non solo genera microtask. In generale, la chiamata rAF crea una coda separata
Zoom, tutto il

67

Concetti di base nelle specifiche :

  • Un ciclo di eventi ha una o più code attività (la coda attività è coda macrotask)
  • Ogni ciclo di eventi ha una coda microtask.
  • coda attività = coda macrotask! = coda microtask
  • un'attività può essere inserita nella coda macrotask o nella coda microtask
  • quando un'attività viene inserita in una coda (micro / macro), intendiamo che la preparazione del lavoro è terminata, quindi l'attività può essere eseguita ora.

E il modello di processo del ciclo di eventi è il seguente:

quando lo stack di chiamate è vuoto, eseguire i passaggi-

  1. selezionare l'attività più vecchia (attività A) nelle code delle attività
  2. se l'attività A è nulla (significa che le code delle attività sono vuote), passare al punto 6
  3. impostare "attività attualmente in esecuzione" su "attività A"
  4. eseguire "task A" (significa eseguire la funzione di callback)
  5. impostare "attività attualmente in esecuzione" su null, rimuovere "attività A"
  6. eseguire la coda microtask
    • (a). selezionare l'attività più vecchia (attività x) nella coda microtask
    • (b). Se l'attività x è nulla (significa che le code di microtask sono vuote), vai al passaggio (g)
    • (c) .set "task attualmente in esecuzione" su "task x"
    • (d) .run "task x"
    • (e) .set "task attualmente in esecuzione" su null, rimuovere "task x"
    • (f) .seleziona l'attività più vecchia successiva nella coda microtask, vai al passaggio (b)
    • (g) coda di microtask finale;
  7. vai al passaggio 1.

un modello di processo semplificato è il seguente:

  1. eseguire l'attività meno recente nella coda macrotask, quindi rimuoverla.
  2. eseguire tutte le attività disponibili nella coda microtask, quindi rimuoverle.
  3. round successivo: esegui l'attività successiva nella coda macrotask (vai al passaggio 2)

qualcosa da ricordare:

  1. quando è in esecuzione un'attività (nella coda macrotask), è possibile che vengano registrati nuovi eventi, quindi è possibile creare nuove attività. Di seguito sono due nuove attività create:
    • Il callback di promiseA.then () è un'attività
      • promiseA è stato risolto / rifiutato: l'attività verrà inserita nella coda di microtask nel round corrente del loop degli eventi.
      • promiseA è in sospeso: l'attività verrà inserita nella coda di microtask nel round successivo del loop degli eventi (potrebbe essere il round successivo)
    • Il callback di setTimeout (callback, n) è un'attività e verrà inserito nella coda macrotask, anche n è 0;
  2. l'attività nella coda microtask verrà eseguita nel round corrente, mentre l'attività nella coda macrotask deve attendere il prossimo round del ciclo degli eventi.
  3. sappiamo tutti che il callback di "click", "scroll", "ajax", "setTimeout" ... sono compiti, tuttavia dovremmo anche ricordare che i codici js nel loro insieme nel tag script sono anche compiti (un macrotask).

2
Questa è un'ottima spiegazione! Grazie per la condivisione!. Un'altra cosa da menzionare è in NodeJs , setImmediate()macro / task ed process.nextTick()è un micro / job.
LeOn - Han Li

6
Che dire delle paintattività del browser ? In quale categoria rientrerebbero?
Legends

Penso che si adatterebbero a micro compiti (come requestAnimationFrame)
Divyanshu Maithani,

Ecco l'ordine in cui viene eseguito il loop degli eventi v8 -> Call Stack || Micro compiti || Coda attività || rAF || Rendering dell'albero || Layout || Paint || <Chiamate native OS per disegnare i pixel su uno schermo> <----- 1) DOM (nuove modifiche), CSSOM (nuove modifiche), struttura di rendering, layout e pittura avvengono dopo richiestaAnimationFrame callback secondo i timer del ciclo di eventi. Questo è il motivo per cui è importante completare le operazioni DOM prima di rAF il più possibile, il resto può andare in rAF. PS: la chiamata di rAF attiverà l'esecuzione di un'attività macro.
Anvesh Checka

Non so se mi sbaglio, ma in un certo senso non sono d'accordo con questa risposta, microtask esegue prima di macrotask. codepen.io/walox/pen/yLYjNRq ?
Walox,

9

Penso che non possiamo discutere il loop degli eventi in separazione dallo stack, quindi:

JS ha tre "pile":

  • stack standard per tutte le chiamate sincrone (una funzione chiama un'altra, ecc.)
  • coda microtask (o coda lavori o stack microtask) per tutte le operazioni asincrone con priorità più alta (process.nextTick, Promises, Object.observe, MutationObserver)
  • coda macrotask (o coda eventi, coda attività, coda macrotask) per tutte le operazioni asincrone con priorità inferiore (setTimeout, setInterval, setImmediate, requestAnimationFrame, I / O, UI rendering)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

E il ciclo degli eventi funziona in questo modo:

  • esegui tutto dal basso verso l'alto dallo stack e SOLO quando lo stack è vuoto, controlla cosa sta succedendo nelle code sopra
  • controlla il micro stack ed esegui tutto lì (se richiesto) con l'aiuto dello stack, un micro task dopo l'altro fino a quando la coda microtask è vuota o non richiede alcuna esecuzione e SOLO quindi controlla lo stack macro
  • controlla lo stack di macro ed esegui tutto lì (se richiesto) con l'aiuto dello stack

Lo stack Mico non verrà toccato se lo stack non è vuoto. Lo stack macro non verrà toccato se il micro stack non è vuoto O non richiede alcuna esecuzione.

Per riassumere: la coda microtask è quasi uguale alla coda macrotask ma quelle attività (process.nextTick, Promises, Object.observe, MutationObserver) hanno una priorità maggiore rispetto ai macrotask.

Micro è come macro ma con priorità più alta.

Qui hai il codice "definitivo" per capire tutto.

console.log('stack [1]');
setTimeout(() => console.log("macro [2]"), 0);
setTimeout(() => console.log("macro [3]"), 1);

const p = Promise.resolve();
for(let i = 0; i < 3; i++) p.then(() => {
    setTimeout(() => {
        console.log('stack [4]')
        setTimeout(() => console.log("macro [5]"), 0);
        p.then(() => console.log('micro [6]'));
    }, 0);
    console.log("stack [7]");
});

console.log("macro [8]");

/* Result:
stack [1]
macro [8]

stack [7], stack [7], stack [7]

macro [2]
macro [3]

stack [4]
micro [6]
stack [4]
micro [6]
stack [4]
micro [6]

macro [5], macro [5], macro [5]
--------------------
but in node in versions < 11 (older versions) you will get something different


stack [1]
macro [8]

stack [7], stack [7], stack [7]

macro [2]
macro [3]

stack [4], stack [4], stack [4]
micro [6], micro [6], micro [6]

macro [5], macro [5], macro [5]

more info: https://blog.insiderattack.net/new-changes-to-timers-and-microtasks-from-node-v11-0-0-and-above-68d112743eb3
*/

1
Chiamare una coda uno stack è totalmente confuso.
Bergi,
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.