El Event Loop y la Concurrencia en JavaScript
JavaScript es un lenguaje monohilo (single-threaded), lo que significa que posee una única pila de ejecución (Call Stack) y solo puede ejecutar una instrucción a la vez.
Sin embargo, JavaScript es capaz de manejar miles de operaciones asíncronas concurrentes (peticiones de red, temporizadores, eventos de interfaz) sin bloquearse gracias al Event Loop (Bucle de Eventos) y a las APIs proporcionadas por el entorno de ejecución (el navegador o Node.js).
1. Componentes de la Arquitectura del Motor JS #
┌───────────────────────────┐ ┌───────────────────────────┐
│ CALL STACK │ │ Web APIs / Node C++ │
│ (Pila de llamadas síncrona)│ ──────> │ (setTimeout, fetch, DOM) │
└─────────────┬─────────────┘ └─────────────┬─────────────┘
│ │
▼ ▼
┌───────────────┐ ┌───────────────────────────┐
│ EVENT LOOP │ <──────────── │ MICROTASK QUEUE │
│ │ │ (Promise.then, queueMicro)│
│ ¿Stack vacío?│ ├───────────────────────────┤
│ │ <──────────── │ TASK QUEUE (MACRO) │
└───────────────┘ │ (setTimeout, setInterval) │
└───────────────────────────┘
- Call Stack (Pila de llamadas): Registra las funciones que se están ejecutando en orden LIFO (Last In, First Out).
- Web APIs / Entorno: Hilos secundarios del navegador/Node que procesan peticiones HTTP, cuentas regresivas y eventos del DOM sin bloquear la pila principal.
- Microtask Queue (Cola de Microtareas): Cola de alta prioridad para callbacks de promesas (
.then(),.catch(),.finally()),queueMicrotask()yMutationObserver. - Task Queue / Macrotasks (Cola de Macrotareas): Cola para
setTimeout,setInterval,setImmediate(Node) y eventos de usuario. - Event Loop: El vigilante constante que verifica si el Call Stack está vacío. Si está vacío, vacía primero toda la cola de microtareas antes de tomar la siguiente macrotarea.
2. El Orden de Prioridad de Ejecución #
El Event Loop sigue una regla estricta:
- Ejecuta todo el código síncrono hasta vaciar el Call Stack.
- Drena y ejecuta todas las Microtareas acumuladas.
- El navegador realiza el renderizado visual de la página (si procede).
- Toma y ejecuta una sola Macrotarea de la Task Queue.
- Vuelve al paso 2 (revisa microtareas inmediatamente).
3. Ejemplo Práctico de Orden de Ejecución #
Analicemos el siguiente código para predecir su salida exacta:
1console.log("1. Síncrono inicio"); 2 3setTimeout(() => { 4 console.log("2. Macrotarea (setTimeout)"); 5}, 0); 6 7Promise.resolve().then(() => { 8 console.log("3. Microtarea 1 (Promise)"); 9}).then(() => { 10 console.log("4. Microtarea 2 (Promise encadenada)"); 11}); 12 13queueMicrotask(() => { 14 console.log("5. Microtarea 3 (queueMicrotask)"); 15}); 16 17console.log("6. Síncrono fin");Salida de consola 1. Síncrono inicio 6. Síncrono fin 3. Microtarea 1 (Promise) 5. Microtarea 3 (queueMicrotask) 4. Microtarea 2 (Promise encadenada) 2. Macrotarea (setTimeout)
Explicación paso a paso: #
- Se ejecutan
1y6síncronamente en el Call Stack. setTimeout(..., 0)delega el temporizador a la Web API y se encola en la Task Queue (Macrotarea).- Las promesas y
queueMicrotaskse encolan en la Microtask Queue. - Al vaciarse el Call Stack, el Event Loop procesa todas las microtareas (
3,5,4). - Finalmente, procesa la macrotarea de
setTimeout(2).
4. queueMicrotask(): Encolar Microtareas Manualmente #
La función global queueMicrotask(callback) permite programar una función para que se ejecute justo antes de ceder el control al renderizado o a la siguiente macrotarea, sin necesidad de instanciar un Promise.resolve():
1function logOperacion(id) { 2 console.log(`Operación ${id} iniciada`); 3 queueMicrotask(() => { 4 console.log(`[Auditoría] Operación ${id} procesada`); 5 }); 6} 7 8logOperacion(42); 9console.log("Código posterior en la función principal");Salida de consola Operación 42 iniciada Código posterior en la función principal [Auditoría] Operación 42 procesada
Resumen del tema
Conceptos clave #
- Naturaleza Monohilo: JavaScript tiene un único Call Stack principal para ejecución de código.
- Diferencia Microtareas vs Macrotareas:
- Microtareas: Promesas (
.then),queueMicrotask(),async/await. Prioridad máxima inmediata tras vaciar el Call Stack. - Macrotareas:
setTimeout,setInterval, I/O, renderizado. Se ejecutan una por ciclo del bucle.
- Microtareas: Promesas (
- Algoritmo del Event Loop: Código Síncrono Drenaje total de Microtareas Render UI 1 Macrotarea.
Qué debes recordar #
Las microtareas (promesas y queueMicrotask) siempre se ejecutan antes que las macrotareas (setTimeout) tan pronto como el Call Stack queda libre.