Diferencia entre microtask y macrotask dentro de un contexto de bucle de eventos

140

Acabo de leer la especificación Promesas / A + y me topé con los términos microtask y macrotask: consulte http://promisesaplus.com/#notes

Nunca he oído hablar de estos términos antes, y ahora tengo curiosidad por saber cuál sería la diferencia.

Ya he intentado encontrar información en la web, pero todo lo que he encontrado es esta publicación de los Archivos de w3.org (que no me explica la diferencia): http://lists.w3.org/Archives /Public/public-nextweb/2013Jul/0018.html

Además, he encontrado un módulo npm llamado "macrotask": https://www.npmjs.org/package/macrotask Nuevamente, no se aclara cuál es exactamente la diferencia.

Todo lo que sé es que tiene algo que ver con el bucle de eventos, como se describe en https://html.spec.whatwg.org/multipage/webappapis.html#task-queue y https: //html.spec.whatwg .org / multipage / webappapis.html # perform-a-microtask-checkpoint

Sé que en teoría debería poder extraer las diferencias yo mismo, dada esta especificación WHATWG. Pero estoy seguro de que otros podrían beneficiarse también de una breve explicación dada por un experto.

NicBright
fuente
En resumen: múltiples colas de eventos anidados. Incluso podría implementar uno usted mismo:while (task = todo.shift()) task();
Bergi
1
Para alguien que quiere un poco más de detalles: Secretos del JavaScript Ninja, 2a edición, CAPÍTULO 13 Eventos sobrevivientes
Ethan

Respuestas:

220

Una vuelta del bucle de eventos tendrá exactamente una tarea procesada desde la cola macrotask (esta cola simplemente se llama cola de tareas en la especificación WHATWG ). Una vez que esta macrotask haya terminado, se procesarán todas las microtask disponibles , es decir, dentro del mismo ciclo de recorrido . Mientras se procesan estas microtask, pueden poner en cola aún más microtasks, que se ejecutarán una por una, hasta que se agote la cola de microtask.

¿Cuáles son las consecuencias prácticas de esto?

Si una microtask pone en cola recursivamente a otras microtask, puede pasar mucho tiempo hasta que se procese la próxima macrotask. Esto significa que podría terminar con una IU bloqueada, o algunas E / S terminadas inactivas en su aplicación.

Sin embargo, al menos con respecto a la función process.nextTick de Node.js (que pone en cola las microtask ), existe una protección incorporada contra dicho bloqueo mediante process.maxTickDepth. Este valor se establece en un valor predeterminado de 1000, lo que reduce el procesamiento posterior de microtask después de alcanzar este límite, lo que permite procesar la próxima macrotask )

Entonces, ¿cuándo usar qué?

Básicamente, use microtasks cuando necesite hacer cosas de forma asíncrona de forma sincrónica (es decir, cuando diría que realice esta (micro) tarea en el futuro más inmediato ). De lo contrario, se adhieren a macrotasks .

Ejemplos

macrotasks: setTimeout , setInterval , setImmediate , requestAnimationFrame , I / O , UI rendering
microtasks: process.nextTick , Promises , queueMicrotask , MutationObserver

NicBright
fuente
44
Aunque hay un punto de control de microtask en el bucle de eventos, aquí no es donde la mayoría de los desarrolladores encontrarán microtask. Las micrototas se procesan cuando la pila JS se vacía. Esto puede suceder muchas veces dentro de una tarea, o incluso dentro de los pasos de representación del bucle de eventos.
JaffaTheCake
2
process.maxTickDepthfue eliminado hace mucho tiempo: github.com/nodejs/node/blob/…
RidgeA
También puede usar el método queueMicrotask () para agregar un nuevo
microtask
Gracias @ZoomAll, no sabía queueMicrotask () hasta ahora. Lo he agregado a la respuesta más enlaces a todas las cosas ...
NicBright
requestAnimationFrame (rAF) no solo genera microtask. En general, la llamada rAF crea una cola separada
ZoomAll
67

Conceptos básicos en especificación :

  • Un bucle de eventos tiene una o más colas de tareas (la cola de tareas es cola macrotask)
  • Cada ciclo de evento tiene una cola de microtask.
  • cola de tareas = cola de macrotask! = cola de microtask
  • una tarea se puede insertar en la cola de macrotask o en la cola de microtask
  • Cuando una tarea se inserta en una cola (micro / macro), queremos decir que la preparación del trabajo ha finalizado, por lo que la tarea se puede ejecutar ahora.

Y el modelo de proceso de bucle de eventos es el siguiente:

cuando la pila de llamadas está vacía, siga los pasos:

  1. seleccione la tarea más antigua (tarea A) en las colas de tareas
  2. si la tarea A es nula (significa que las colas de tareas están vacías), vaya al paso 6
  3. establezca "tarea actualmente en ejecución" en "tarea A"
  4. ejecutar "tarea A" (significa ejecutar la función de devolución de llamada)
  5. establezca "tarea actualmente en ejecución" en nulo, elimine "tarea A"
  6. realizar la cola de microtask
    • (a) .seleccione la tarea más antigua (tarea x) en la cola de microtask
    • (b) .si la tarea x es nula (significa que las colas de microtask están vacías), vaya al paso (g)
    • (c) .set "tarea actualmente en ejecución" a "tarea x"
    • (d) .run "tarea x"
    • (e) .set "tarea actualmente en ejecución" a nulo, elimine "tarea x"
    • (f) .seleccione la siguiente tarea más antigua en la cola de microtask, vaya al paso (b)
    • (g) .finish microtask queue;
  7. salta al paso 1.

Un modelo de proceso simplificado es el siguiente:

  1. ejecute la tarea más antigua en la cola de macrotask, luego quítela.
  2. ejecute todas las tareas disponibles en la cola de microtask, luego quítelas.
  3. siguiente ronda: ejecutar la siguiente tarea en la cola de macrotask (paso 2 de salto)

Algo para recordar:

  1. cuando se está ejecutando una tarea (en la cola de macrotask), se pueden registrar nuevos eventos, por lo que se pueden crear nuevas tareas. A continuación hay dos nuevas tareas creadas:
    • La devolución de llamada de promiseA.then () es una tarea
      • Se resuelve / rechaza la promesa A: la tarea se incluirá en la cola de microtask en la ronda actual del bucle de eventos.
      • promiseA está pendiente: la tarea se incluirá en la cola de microtask en la futura ronda del ciclo de eventos (puede ser la próxima ronda)
    • La devolución de llamada de setTimeout (callback, n) es una tarea, y será empujada a la cola de macrotask, incluso n es 0;
  2. la tarea en la cola de microtask se ejecutará en la ronda actual, mientras que la tarea en la cola de macrotask debe esperar a la próxima ronda del ciclo de eventos.
  3. todos sabemos que la devolución de llamada de "clic", "desplazamiento", "ajax", "setTimeout" ... son tareas, sin embargo, también debemos recordar que los códigos js en su conjunto en la etiqueta del script es una tarea (un macrotask) también.
wengeezhang
fuente
2
Esta es una gran explicación! ¡Gracias por compartir!. Una cosa más que mencionar está en NodeJs , setImmediate()es macro / tarea y process.nextTick()es un micro / trabajo.
LeOn - Han Li
66
¿Qué pasa con las painttareas del navegador ? ¿En qué categoría encajarían?
Legends
Creo que encajarían en micro tareas (como requestAnimationFrame)
Divyanshu Maithani
Aquí está el orden en que se ejecuta el bucle de eventos v8 -> Call Stack || Micro Tareas || Cola de tareas || rAF || Render Tree || Diseño || Pintura || <OS Native llama para dibujar los píxeles en una pantalla> <----- 1) DOM (nuevos cambios), CSSOM (nuevos cambios), árbol de renderizado, diseño y pintura ocurren después de la devolución de llamada requestAnimationFrame según los temporizadores de bucle de eventos. Es por eso que es importante terminar sus operaciones DOM antes de rAF tanto como pueda, el resto puede ir en rAF. PD: llamar a rAF activará una ejecución de macro tarea.
Anvesh Checka
No sé si estoy equivocado, pero no estoy de acuerdo con esta respuesta, microtasks se ejecuta antes que macrotask. codepen.io/walox/pen/yLYjNRq ?
Walox
9

Creo que no podemos discutir el bucle de eventos en separación de la pila, así que:

JS tiene tres "pilas":

  • pila estándar para todas las llamadas síncronas (una función llama a otra, etc.)
  • cola de microtask (o cola de trabajos o pila de microtask) para todas las operaciones asíncronas con mayor prioridad (process.nextTick, Promises, Object.observe, MutationObserver)
  • cola de macrotask (o cola de eventos, cola de tareas, cola de macrotask) para todas las operaciones asincrónicas con menor prioridad (setTimeout, setInterval, setImmediate, requestAnimationFrame, I / O, UI rendering)
|=======|
| macro |
| [...] |
|       |
|=======|
| micro |
| [...] |
|       |
|=======|
| stack |
| [...] |
|       |
|=======|

Y el bucle de eventos funciona de esta manera:

  • ejecute todo de abajo hacia arriba desde la pila, y SOLO cuando la pila esté vacía, verifique lo que sucede en las colas de arriba
  • compruebe la micro pila y ejecute todo lo que esté allí (si es necesario) con la ayuda de la pila, una micro tarea tras otra hasta que la cola de microtask esté vacía o no requiera ejecución y SOLO luego verifique la macro pila
  • compruebe la pila de macros y ejecute todo allí (si es necesario) con la ayuda de la pila

La pila Mico no se tocará si la pila no está vacía. La pila de macros no se tocará si la micro pila no está vacía O no requiere ninguna ejecución.

En resumen: la cola de microtask es casi lo mismo que la cola de macrotask, pero esas tareas (process.nextTick, Promises, Object.observe, MutationObserver) tienen mayor prioridad que las macrotasks.

Micro es como macro pero con mayor prioridad.

Aquí tienes el código "definitivo" para entender todo.

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
*/
usuario1660210
fuente
1
Llamar a una cola una pila es totalmente confuso.
Bergi