Event Loop, microtasks y macrotasks en JavaScript
Domina el Event Loop de JavaScript y entiende cómo la asincronía evita que tus aplicaciones se bloqueen. Aprende la diferencia clave de prioridad entre microtasks (promesas) y macrotasks (timeouts) para optimizar el rendimiento. Esta guía técnica te explica el flujo de ejecución y el renderizado del navegador paso a paso.

Índice
1. ¿Qué es event loop?
Para entender el Event Loop, primero debemos comprender cómo «piensa» el navegador cuando abrimos una página web. No es un proceso mágico; es una cadena de montaje de alta precisión.
1.1. El Renderizado: El escenario donde todo comienza
Cuando entras en un sitio, el navegador comienza a leer el documento HTML de arriba hacia abajo. Este proceso se llama Parsing. Su objetivo no es mostrar el texto de inmediato, sino crear un «mapa mental» de la estructura llamado DOM (Document Object Model).
Sin embargo, cuando este flujo de lectura línea por línea se encuentra con un «obstáculo» necesario: las etiquetas <script>. Aquí es donde la arquitectura del navegador se divide en dos mundos:
- El Motor de JavaScript (El Ejecutor):
- Cuando el navegador encuentra código JS, detiene momentáneamente la construcción del DOM para ejecutarlo, y envía dicho
<script>al motor de JavaScript para leerlo línea por línea (de forma síncrona). Si el código es simple, lo resuelve y sigue adelante.
- Cuando el navegador encuentra código JS, detiene momentáneamente la construcción del DOM para ejecutarlo, y envía dicho
- El Problema del Bloqueo:
- Si una de esas líneas de código fuera una tarea pesada (como pedir datos a un servidor que tarda 10 segundos), el navegador se congelaría. No podrías hacer scroll ni ver el resto de la página porque el «hilo principal» está ocupado.
1.2. La Asincronía: Delegar para no detenerse
Aquí es donde entra la verdadera utilidad del Event Loop. Cuando el motor de JavaScript detecta una tarea asíncrona (como un setTimeout o una petición fetch):
- No se queda esperando
- En lugar de detenerse, el motor le «pasa la pelota» a las Web APIs del navegador.
- Delegación inteligente
- El navegador se encarga de realizar esa tarea pesada en segundo plano (en otro hilo), mientras que el motor de JavaScript queda libre para seguir leyendo las siguientes líneas de código sin interrupciones.
1.3. El Viaje de Regreso: De la Web API al Navegador
Una vez que el motor de JavaScript ha delegado una tarea (como una petición de datos o un temporizador) a las Web APIs, el proceso no termina ahí. Es aquí donde el Event Loop demuestra por qué es el corazón de la asincronía.
1.3.1. La Resolución en Segundo Plano
Mientras tú sigues navegando o interactuando con la página, la Web API trabaja en su propio hilo. Cuando la tarea se completa (por ejemplo, el servidor responde con los datos solicitados), la Web API genera un Callback (una función de respuesta).
1.3.2. Las «Salas de Espera»: Microtasks y Macrotasks
La Web API no puede inyectar el resultado directamente en el código que se está ejecutando. Debe ponerlo en una fila de espera según su tipo:
- Microtask Queue (Prioridad VIP)
-
Aquí van las Promesas (
.then,async/await). Son tareas urgentes que el navegador quiere resolver cuanto antes. - Callback Queue / Macrotasks (Prioridad Normal)
-
Aquí van los
setTimeout,setIntervaly eventos del DOM (como clics). Se ejecutan después de que se vacía la Microtask Queue.
1.3.3. El Event Loop: El Coordinador Infatigable
Aquí ocurre el paso más crítico. El Event Loop es un ciclo que pregunta constantemente: «¿Está vacía la Pila de Ejecución (Call Stack)?».
- Si el Call Stack está ocupado
- El Event Loop espera. No interrumpe lo que JavaScript está haciendo en ese momento.
- Si el Call Stack se vacía
- El Event Loop primero limpia toda la fila de Microtareas (VIP). Solo cuando no queda ni una sola promesa pendiente, toma una tarea de la Callback Queue y la sube al escenario principal (Call Stack).
1.3.4. La Renderización Final
Una vez que el resultado llega al Call Stack, JavaScript ejecuta la lógica (por ejemplo, insertar una noticia en el HTML). Al terminar esa ejecución, el navegador aprovecha para realizar un Repaint (redibujado) de la pantalla. Ahora, el usuario finalmente ve el resultado de esa tarea que antes estaba «en segundo plano».
DATO CLAVE! Esta es la razón por la cual una Promesa siempre se ejecutará antes que un setTimeout(..., 0). El Event Loop es un guardián que siempre prioriza la fila VIP antes de dejar pasar a la fila común.
2. ¿Cómo funciona el event loop exactamente?
Vamos a desglosar esta respuesta conjuntamente con una imagen:

Imaginala como un mapa de tráfico que dicta hacia dónde fluye el código desde que lo escribís hasta que se muestra en la pantalla.
Aquí tenés la explicación técnica de lo que está pasando en cada sección:
2.1. El Motor de JavaScript (Lado Izquierdo)
Es el «cerebro» donde empieza todo. Aquí es donde el navegador encuentra un <script> y ahora el motor de JS se hace cargo:
- Call Stack (Pila de Ejecución)
-
Es donde JS ejecuta tu código línea por línea. Funciona con el principio LIFO (lo último que entra es lo primero que sale). Si una función es síncrona (como un simple
console.log), entra, se ejecuta y sale volando. - Heap (Memoria)
- Es simplemente el depósito de basura y tesoros. Ahí es donde se guardan los objetos, variables y funciones que definís.
2.2. La Delegación (Paso 2 en la imagen)
Cuando el Call Stack encuentra algo que no puede hacer solo (como esperar 3 segundos o pedir datos a un servidor), no se queda bloqueado.
¿Qué hace?:
Le «patea» la tarea al Entorno (el cuadro de la derecha). Esto es lo que ves en la flecha azul que dice «Delega tarea asíncrona».
2.3. El Entorno: Web APIs / C++ APIs (Lado Derecho)
Este es el «gimnasio» donde las tareas pesadas se ponen a trabajar fuera del hilo principal.
- Aquí, el navegador se encarga de contar el tiempo del
setTimeout, esperar la respuesta de unfetch(petición de red) o vigilar si hiciste un clic. - LO CLAVE:
- Esto sucede en hilos separados del navegador (segundo plano), por eso no congela tu página.
2.4. Las Colas de Espera (Bajo el Entorno)
Una vez que la Web API termina (por ejemplo, pasaron los 3 segundos), el resultado no puede saltar directo al Call Stack. Debe hacer fila en las Queues:
- Microtask Queue (VIP):
- Es la fila naranja. Aquí van las Promesas. Tienen prioridad absoluta.
- Callback Queue (Normal):
- Es la fila azul. Aquí van los
setTimeouty los eventos. Solo se atienden cuando la fila VIP está vacía.
- Es la fila azul. Aquí van los
2.5. El Event Loop: El Gran Coordinador (Centro)
Ese círculo con flechas en el medio es el corazón del sistema. Su trabajo es un bucle infinito que hace lo siguiente:
- Mira el Call Stack:
- ¿Está vacío? (Representado por el ícono del ojo).
- Si está vacío:
- Primero vacía toda la Microtask Queue (las Promesas).
- Después:
- Pasa una sola tarea de la Callback Queue al Call Stack para que se ejecute.
- Repite:
- Vuelve a empezar el ciclo.
3. Ejemplo práctico de un event loop
Imagina este flujo en tu código:
- HTML: Se renderiza el título «Noticias del Día».
- JS Sincrónico: Se imprime «Cargando aplicación…».
- JS Asíncrono (Promesa): Se piden los datos del clima (prioridad alta).
- JS Asíncrono (Timeout): Se programa un banner publicitario (prioridad normal).
- JS Sincrónico: Se imprime «Interfaz lista».
El Proceso Paso a Paso (El Ciclo Real)
| Paso | Acción | ¿Dónde está ocurriendo? |
|---|---|---|
| 1 | Se ejecuta console.log("Cargando..."). |
Call Stack (entra y sale al instante). |
| 2 | Se encuentra el fetch (Promesa) y el setTimeout. |
El Call Stack los detecta y los manda a la Web API. |
| 3 | Mientras el navegador (Web API) espera los datos, JS no se detiene. | Call Stack ejecuta console.log("Interfaz lista"). |
| 4 | El «Momento de las Filas»: La Web API termina sus tareas. | Envía el clima a la Microtask Queue y el banner a la Callback Queue. |
| 5 | El Event Loop entra en acción: ve que el Call Stack está vacío. | Primero vacía toda la Microtask Queue (imprime el clima). |
| 6 | Final del ciclo: una vez que no quedan microtareas… | El Event Loop toma el banner de la Callback Queue y lo mete al Call Stack. |
Es fundamental entender que la Web API no ejecuta el código de JS, solo gestiona la espera externa.
3.1. Ejemplo de código para que pruebes:
console.log("1. Inicio Sincrónico");
setTimeout(() => {
console.log("2. Macrotarea (Callback Queue)");
}, 0);
Promise.resolve().then(() => {
console.log("3. Microtarea (Microtask Queue) - VIP");
});
console.log("4. Fin Sincrónico");Lenguaje del código: JavaScript (javascript)
El orden de salida siempre será: 1 -> 4 -> 3 -> 2.
NOTA! Incluso si el setTimeout tiene 0 milisegundos, la Promesa (Microtarea) le ganará la carrera porque el Event Loop siempre limpia la fila VIP antes de mirar la fila común.
Bien, ahora veamos un ejemplo escenario real para entender cómo trabaja el event loop
4.2. Ejemplo: Escenario real del Event Loop en acción
Para entender cómo interactúan todas estas piezas, imaginemos un proceso cotidiano: estás en una tienda online (como Amazon) y quieres buscar un producto. Aquí es donde el Event Loop de tu navegador trabaja incansablemente para que la página no se «congele» mientras esperas los resultados.
ESCENARIO: Buscando un par de zapatillas
4.2.1. El Clic (Call Stack)
Escribes «zapatillas» y haces clic en el botón «Buscar». Esta acción dispara una función que entra directamente al Call Stack (la pila de ejecución). Como JavaScript es de un solo hilo, si el motor tuviera que procesar la búsqueda de miles de productos por sí mismo, la página se bloquearía por completo y no podrías ni mover el ratón.
4.2.2. La Petición de Datos (Web API)
En lugar de detenerse a esperar, JavaScript le delega el trabajo pesado al navegador: «Oye, haz esta petición al servidor (fetch) para traer las zapatillas y avísame cuando termines». Esta tarea se traslada a las Web APIs. El Call Stack se libera inmediatamente, permitiéndote seguir haciendo scroll o abriendo el menú lateral sin retrasos.
4.2.3. El Trabajo en Segundo Plano (Background)
Mientras el servidor busca las zapatillas en la base de datos, la tarea está técnicamente «fuera» del motor de JavaScript. El navegador gestiona la conexión de red de forma independiente, utilizando sus propios recursos de sistema.
4.2.4. La Respuesta Lista (Callback/Microtask Queue)
Cuando llegan los datos (por ejemplo, los precios y fotos de 20 zapatillas), el navegador deposita una «nota» en la Queue que dice: «Aquí están los datos, ejecuta la función para dibujarlos en la pantalla». Si es una Promesa (fetch), irá a la fila VIP de Microtareas.
4.2.5. El Event Loop en acción
El Event Loop es el vigilante que pregunta constantemente: ¿Está el Call Stack vacío?. En cuanto dejas de interactuar por un milisegundo y el Stack queda libre, el Event Loop detecta la respuesta de las zapatillas en la fila y la «sube» al Stack para que se procese.
4.2.6. Renderizado (Resultado Final)
¡Mágicamente aparecen las zapatillas en tu pantalla! Todo este ciclo ocurrió en milisegundos, permitiendo que la interfaz se mantuviera fluida y reactiva en todo momento.
¿Qué pasaría si NO existiera el Event Loop?
Sin este mecanismo, al hacer clic en «Buscar», el navegador entraría en un estado de bloqueo total. La ventana se quedaría «colgada» (posiblemente verías el cursor de espera infinito) y no podrías cerrar pestañas, mover el cursor ni escribir hasta que el servidor respondiera. El Event Loop es, literalmente, lo que hace que la web moderna sea usable.
4. Las microtasks y las macrotasks
4.1. ¿Qué son las Microtasks? (La Fila VIP)
Las microtareas son tareas de alta prioridad que deben ejecutarse inmediatamente después de que el código actual (síncrono) termine, pero antes de que el navegador siga con cualquier otra cosa (como renderizar o ejecutar un temporizador).
- ¿QUIENES LAS GENERAN?: Principalmente las Promesas (
.then(),.catch(),.finally()), la API deMutationObservery el proceso deasync/await. - REGLA DE ORO: El Event Loop no pasará a la siguiente macrotarea hasta que la cola de microtareas esté completamente vacía. Si una microtarea genera otra microtarea, esa también se ejecuta en el mismo ciclo.
4.2. ¿Qué son las Macrotasks? (La Fila General)
También llamadas simplemente «Tasks». Son operaciones más pesadas o que dependen de eventos externos del sistema o del navegador.
- ¿QUIENES LAS GENERAN?:
setTimeout,setInterval, eventos del DOM (clic, scroll), peticiones de red (XMLHttpRequest) ysetImmediate(en Node.js). - REGLA DE ORO: El Event Loop toma solo una macrotarea de la cola, la ejecuta, y luego vuelve a revisar si hay microtareas nuevas antes de pasar a la siguiente macrotarea.
4.3. Diferencias Clave
La diferencia fundamental no es solo «qué son», sino cuándo se ejecutan. Aquí tienes una tabla comparativa para tu artículo:
| Característica | Microtasks (VIP) | Macrotasks (General) |
|---|---|---|
| Ejemplos | Promises, async/await. |
setTimeout, setInterval, eventos. |
| Prioridad | Máxima. Se ejecutan antes que cualquier tarea. | Normal. Esperan su turno. |
| Ejecución | Se vacía la cola completa en cada ciclo. | Se ejecuta una por una por cada ciclo del Event Loop. |
| Renderizado | Bloquean el renderizado hasta terminar todas. | Permiten el renderizado entre tareas. |
4.4. ¿Cómo afecta esto a async/await?
Es común confundirse, pero async/await es simplemente «azúcar sintáctico» sobre las Promesas.
- Cuando usas
await, el código que sigue a esa línea se envía automáticamente a la Microtask Queue. - Esto garantiza que tu lógica asíncrona se ejecute lo más rápido posible, adelantándose a cualquier
setTimeoutque esté esperando.
5. Problemas comunes al trabajar con asincronía y el Event Loop
Trabajar con asincronía en JavaScript es como manejar una orquesta: si un músico se desfasa, toda la pieza suena mal. Al ser un lenguaje de un solo hilo, cualquier error en la gestión del Event Loop tiene consecuencias directas en la experiencia del usuario.
Aquí te presento los problemas más comunes y críticos que todo desarrollador debe conocer.
5.1. El bloqueo del hilo principal (Blocking the Main Thread)
Este es el error número uno. Ocurre cuando ejecutas un código síncrono que tarda demasiado tiempo en el Call Stack.
- El problema: Como el Event Loop solo puede procesar una cosa a la vez, si el Call Stack está ocupado con un bucle gigante o un cálculo matemático complejo, el navegador no puede renderizar, no detecta clics y la página se siente «congelada».
- La solución: Dividir las tareas pesadas en fragmentos más pequeños usando
setTimeouto delegarlas a un Web Worker (que corre en un hilo separado).
5.2. Event Loop Starvation (Inanición)
Este es un problema sutil pero devastador que ocurre específicamente con la Microtask Queue.
- El problema: El Event Loop tiene la regla de vaciar toda la cola de microtareas antes de pasar a la siguiente macrotarea o al renderizado. Si una promesa genera otra promesa, y esa otra, de forma infinita o muy rápida, el Call Stack nunca se libera para el resto del sistema.
- Consecuencia: El navegador nunca llega al paso de Repaint/Reflow, y la interfaz queda totalmente bloqueada aunque el código técnico esté «funcionando».
5.3. Callback Hell (Callbacks Anidados)
Antes de las Promesas y el async/await, este era el dolor de cabeza principal de los programadores de JS.
- El problema: Para realizar tareas secuenciales (primero A, luego B, luego C), terminas anidando funciones dentro de otras funciones.
- Consecuencia: El código se vuelve una «pirámide» imposible de leer, de depurar y, sobre todo, de manejar errores, ya que cada nivel necesita su propio control de excepciones.
// Ejemplo de Callback Hell
getData(function(a) {
getMoreData(a, function(b) {
getEvenMoreData(b, function(c) {
console.log(c);
});
});
});Lenguaje del código: JavaScript (javascript)
5.4. Errores de «Timing» (Race Conditions)
La asincronía no garantiza el orden de llegada, solo el orden de salida.
- El problema: Suponer que una tarea terminará antes que otra solo porque se disparó primero.
- Ejemplo típico: Disparas dos peticiones
fetch. La segunda es más rápida y llega primero, sobreescribiendo los datos de la primera de forma inesperada. - La solución: Usar flags de control o herramientas como
Promise.allpara coordinar los resultados.
5.5. El «Try/Catch» Silencioso
Este es un error técnico muy frecuente al empezar con asincronía.
- El problema: Un bloque
try/catchsíncrono no puede capturar un error que ocurre dentro de una tarea asíncrona (como unsetTimeouto una Promesa sinawait). - Ejemplo:
try {
setTimeout(() => {
throw new Error("¡Boom!"); // El try/catch ya terminó cuando esto explota
}, 100);
} catch (e) {
console.log("Atrapado"); // Nunca se ejecuta
}Lenguaje del código: JavaScript (javascript)
- La solución: Usar
.catch()en promesas o siempre usarawaitdentro de bloquestry/catch.
