DMarlon Studio

Lección 14 · 14 de 20

El bucle de eventos · JavaScript

Entienda en qué orden ejecuta JavaScript el código síncrono, las microtareas (promesas, nextTick) y las macrotareas (setTimeout) en Node.

JavaScript · El bucle de eventos en DMarlon Studio: el código del ejemplo y su salida en la terminal

JavaScript ejecuta el código en un solo hilo, y el bucle de eventos decide qué toca después. La regla es: primero todo el código síncrono; después, todas las microtareas pendientes; luego, una macrotarea (como un temporizador), otra vez todas las microtareas, y así sucesivamente. Los números de la salida muestran ese orden, que no es el del fichero.

El código síncrono va primero: las líneas 1, 2 y 3. Llamar a checkout() ejecuta su cuerpo en el acto hasta el primer await; por eso 2 aparece antes que 3. Los dos setTimeout(..., 0) no se ejecutan «ya»: se apuntan en la cola de temporizadores.

Al terminar el script, Node vacía las microtareas. process.nextTick tiene su propia cola, que en Node va incluso antes que la de las promesas (4). Después, por orden de llegada, el then (5), queueMicrotask (6) y la continuación de checkout tras el await (7): reanudar una función async también es una microtarea.

Solo entonces se ejecutan los temporizadores. La microtarea creada dentro de timeout A (9) va antes que timeout B (10), porque tras cada macrotarea se vacían las microtareas. La conclusión práctica: un then siempre se ejecuta antes que un setTimeout(..., 0), y una cadena de microtareas sin fin dejaría los temporizadores esperando para siempre.

Código · main.js

// The numbers show the order Node guarantees, not the order in the file.console.log("1. script starts (synchronous code)");setTimeout(() => {  console.log("8. timeout A (macrotask)");  Promise.resolve().then(() => console.log("9. microtask created by timeout A"));}, 0);setTimeout(() => console.log("10. timeout B (macrotask)"), 0);Promise.resolve().then(() => console.log("5. promise.then (microtask)"));queueMicrotask(() => console.log("6. queueMicrotask (microtask)"));process.nextTick(() => console.log("4. process.nextTick (before promises)"));async function checkout() {  console.log("2. checkout runs synchronously until its first await");  await null;  console.log("7. checkout resumes after await (microtask)");}checkout();console.log("3. script ends: now the microtasks, then the timers");

Salida · node main.js

1. script starts (synchronous code)
2. checkout runs synchronously until its first await
3. script ends: now the microtasks, then the timers
4. process.nextTick (before promises)
5. promise.then (microtask)
6. queueMicrotask (microtask)
7. checkout resumes after await (microtask)
8. timeout A (macrotask)
9. microtask created by timeout A
10. timeout B (macrotask)

Para recordar

  • Primero el código síncrono, después las microtareas y luego las macrotareas.
  • Las promesas, queueMicrotask y la vuelta de un await son microtareas.
  • setTimeout crea macrotareas, aunque el retraso sea 0.
  • En Node, process.nextTick se ejecuta antes que las promesas.
  • Tras cada macrotarea se vacían todas las microtareas.

Ejercicio

Añada un setTimeout de 0 ms dentro del then de la línea 5 y prediga en qué lugar aparecerá antes de ejecutar el programa; después compruébelo.