Event Loop de JavaScript
Comprende cómo funciona el event loop de JavaScript y cómo escribir código no bloqueante.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Visión General
El event loop de JavaScript es el corazón de la programación asíncrona en navegadores y Node.js. Orquesta la ejecución del código, recopila y procesa eventos, y ejecuta subtareas en cola. Comprender cómo interactúan el call stack, la cola de tareas y la cola de microtareas es esencial para escribir aplicaciones performantes y no bloqueantes.
Cuándo Usar
Usa este recurso cuando:
- Depuras errores asíncronos misteriosos o condiciones de carrera
- Optimizas la responsividad de la UI en aplicaciones frontend
- Eliges entre setTimeout, Promise y queueMicrotask
- Entiendes por qué el orden del código no siempre coincide con el orden de ejecución
Solución
Visualizando el Event Loop
console.log('1. Inicio del script');
setTimeout(() => {
console.log('2. setTimeout (macrotarea)');
}, 0);
Promise.resolve().then(() => {
console.log('3. Promise (microtarea)');
});
queueMicrotask(() => {
console.log('4. queueMicrotask');
});
console.log('5. Fin del script');
// Orden de salida:
// 1. Inicio del script
// 5. Fin del script
// 3. Promise (microtarea)
// 4. queueMicrotask
// 2. setTimeout (macrotarea)
Manejando Tareas de Larga Duración
function procesarArrayGrande(arr, chunkSize = 1000) {
let index = 0;
function procesarChunk() {
const chunk = arr.slice(index, index + chunkSize);
chunk.forEach(item => computacionPesada(item));
index += chunkSize;
if (index < arr.length) {
setTimeout(procesarChunk, 0); // Ceder control al event loop
}
}
procesarChunk();
}
Explicación
El event loop opera en fases:
- Call Stack: Ejecuta código síncrono. Cuando está vacío, el event loop revisa las colas.
- Cola de Microtareas: Procesa callbacks de Promise, queueMicrotask y MutationObserver. Se vacía completamente antes de la siguiente macrotarea.
- Cola de Macrotareas: Procesa setTimeout, setInterval, setImmediate (Node.js) y eventos de I/O.
- Fase de Renderizado: Los navegadores pueden actualizar el DOM y repintar si hay tiempo.
Regla crítica: Todas las microtareas se ejecutan antes de la siguiente macrotarea. Esto puede bloquear la cola de macrotareas si las microtareas encolan más microtareas recursivamente.
Variantes
| Runtime | API Macrotarea | API Microtarea | Notas |
|---|---|---|---|
| Navegador | setTimeout, requestAnimationFrame | Promise, queueMicrotask | rAF corre antes del paint |
| Node.js | setTimeout, setImmediate | Promise, process.nextTick | nextTick corre antes que Promises |
| Deno | setTimeout | Promise, queueMicrotask | Se alinea con comportamiento de navegador |
Mejores Prácticas
- Divide trabajo pesado en chunks: Usa setTimeout o requestIdleCallback para ceder control
- Prefiere microtareas para actualizaciones DOM: queueMicrotask asegura que las lecturas DOM se agrupen
- Evita encolar microtareas recursivamente: Puede congelar el event loop indefinidamente
- Usa requestAnimationFrame para actualizaciones visuales: Se sincroniza con el ciclo de renderizado del navegador
- Perfila con la pestaña Performance: Chrome DevTools visualiza el timing de microtareas y macrotareas
Errores Comunes
- Asumir que setTimeout(0) es inmediato: Siempre es más lento que las microtareas
- Bloquear el hilo principal: Bucles síncronos >50ms causan jank y frames perdidos
- Olvidar nextTick en Node.js: process.nextTick corre antes que Promises, no después
- Recursión de microtareas: Promise.resolve().then(() => Promise.resolve().then(…)) puede bloquear
- Ignorar la fase de renderizado: Colas pesadas de microtareas impiden el pintado del navegador
Preguntas Frecuentes
P: ¿Por qué Promise.then() corre antes que setTimeout(0)? R: Los callbacks de Promise entran en la cola de microtareas, que tiene mayor prioridad que la cola de macrotareas donde viven los callbacks de setTimeout.
P: ¿Cuál es la diferencia entre queueMicrotask y Promise.resolve().then()? R: Funcionalmente idénticos en la mayoría de casos, pero queueMicrotask es más explícito y ligeramente más eficiente.
P: ¿Cómo evito que el event loop se congele? R: Divide el trabajo en chunks pequeños usando setTimeout, requestIdleCallback o Web Workers para tareas intensivas en CPU.
Recursos Relacionados
Prevent Race Conditions in JavaScript Async Code
Identify and fix race conditions in asynchronous JavaScript using proper sequencing, atomic operations, locks, and Promise patterns for predictable concurrent execution
RecipeDeep Clone Objects in JavaScript: Beyond JSON.parse
Compare deep clone strategies including JSON.parse, structuredClone, manual recursion, and library approaches for copying nested objects with circular references and special types
RecipeURL Encoding and Decoding: encodeURI, encodeURIComponent, and Beyond
Master URL encoding in JavaScript and other languages with encodeURI, encodeURIComponent, plus-safe handling, RFC 3986 compliance, and decoding edge cases
RecipeEnable Brotli Compression in Nginx for Faster Asset Delivery
How to configure Brotli compression in Nginx to reduce transfer sizes for JavaScript, CSS, and HTML assets with better ratios than Gzip
RecipeSPA Performance: Code Splitting and Lazy Loading
Improve single-page application load times by splitting bundles at route and component level, implementing lazy loading with React.lazy and dynamic imports