W3docs

Bucle de eventos, microtareas y macrotareas en JavaScript

Aprende cómo funciona el bucle de eventos de JavaScript y cómo se programan las microtareas (Promises) y macrotareas (timers, eventos), con ejemplos ejecutables.

JavaScript ejecuta tu código en un hilo único: ocurre una cosa a la vez, de arriba a abajo. Sin embargo, puede obtener datos, ejecutar timers y responder a clics sin bloquearse. El mecanismo que hace posible esto es el bucle de eventos, y el trabajo que programa se divide en dos tipos de tareas: microtareas y macrotareas. Esta página explica qué es cada una, el orden en que se ejecutan y los errores comunes que suelen confundir a los desarrolladores — todos los ejemplos son ejecutables para que puedas comprobar el resultado por ti mismo.

Cómo Funciona el Bucle de Eventos

El bucle de eventos es el planificador que decide qué fragmento de código se ejecuta a continuación. Para entenderlo, solo necesitas tres partes:

  1. Pila de llamadas — donde tu código se ejecuta realmente. Las funciones se apilan al ser llamadas y se desapilan al retornar. JavaScript ejecuta lo que haya en la pila hasta completarlo antes de hacer cualquier otra cosa; esta es la regla de ejecución hasta completar.
  2. Heap — la memoria donde viven tus objetos. No está directamente involucrada en la planificación, pero es la tercera parte que la gente espera ver mencionada.
  3. Colas de tareas — trabajo pendiente que espera a que la pila esté vacía. Hay dos: la cola de macrotareas (timers, eventos de interfaz, I/O) y la cola de microtareas (callbacks de Promise y queueMicrotask).

Un turno del bucle de eventos funciona así:

  1. Ejecutar la tarea actual en la pila hasta que esté completamente vacía.
  2. Vaciar la cola de microtareas completa — incluidas las microtareas añadidas mientras se vacía.
  3. (En el navegador) renderizar cualquier actualización visual pendiente.
  4. Tomar una macrotarea de la cola de macrotareas y ejecutarla, luego volver al paso 2.

La asimetría clave: después de cada macrotarea, el motor vacía todas las microtareas, pero solo toma una macrotarea por turno de bucle. Esa única regla explica casi todas las sorpresas de ordenación que encontrarás.

Aquí está la demostración más sencilla posible, usando setTimeout para programar una macrotarea:

javascript— editable

En este ejemplo:

  1. console.log('Start'); se ejecuta primero, imprimiendo "Start" en la consola.
  2. setTimeout programa un callback para que se ejecute después de al menos 1000 milisegundos. Retorna de inmediato y no bloquea las líneas siguientes.
  3. console.log('End'); se ejecuta inmediatamente, imprimiendo "End".
  4. Solo después de que el script síncrono termine (y haya transcurrido el tiempo de espera) el bucle de eventos extrae el callback de setTimeout de la cola de macrotareas y lo ejecuta, imprimiendo "Timeout Callback".

El resultado es Start, End, y luego Timeout Callback — el callback del timer espera aunque fue escrito en el medio. El callback de setTimeout es una macrotarea: solo se ejecuta después de que el script en ejecución y todas las microtareas pendientes hayan terminado. Eso es lo que mantiene la página responsiva — el código síncrono nunca tiene que esperar a un timer o solicitud de red.

Microtareas vs. Macrotareas

¿Qué son las Macrotareas?

Una macrotarea (también llamada simplemente "tarea") es una unidad de trabajo independiente y autocontenida que el motor toma una vez por turno de bucle. Las fuentes más comunes son:

  • setTimeout / setInterval: timers que ejecutan un callback después de un tiempo de espera o de forma repetida.
  • Eventos DOM: un manejador de click, scroll o input.
  • I/O: respuestas de red, lecturas de archivos y similares.

El motor ejecuta exactamente una macrotarea, luego vacía todas las microtareas y luego (en el navegador) puede renderizar, antes de tomar la siguiente macrotarea. Por eso las macrotareas nunca se ejecutan una tras otra sin que la cola de microtareas se vacíe entre ellas.

¿Qué son las Microtareas?

Una microtarea es una tarea breve que el motor quiere terminar tan pronto como termine la unidad de código actual — antes de ceder el control a la siguiente macrotarea o al renderizado. Provienen de:

  • Callbacks de Promise: las funciones pasadas a .then(), .catch() y .finally(), más el cuerpo de una función async después de un await.
  • queueMicrotask(fn): una función incorporada que programa una función directamente en la cola de microtareas.

La diferencia crucial: después de la tarea actual, el motor vacía la cola de microtareas completa antes de hacer cualquier otra cosa. Si una microtarea programa otra microtarea, esa nueva también se ejecuta en el mismo vaciado — antes de que la siguiente macrotarea tenga turno.

Ejemplos de Código del Mundo Real

Ejemplo 1: Un timer es una macrotarea

Imagina que quieres mostrar un mensaje después de 2 segundos. La línea de programación se ejecuta ahora; el callback queda en la cola de macrotareas hasta que transcurra el tiempo y la pila esté libre.

javascript— editable

Explicación: setTimeout retorna de inmediato, por lo que ambas líneas console.log fuera de él se ejecutan primero. El callback es una macrotarea que solo se ejecuta después de que el script síncrono termine y el timer dispare. En un navegador, normalmente actualizarías el DOM dentro del callback, por ejemplo document.getElementById('message').textContent = 'Hello there!';.

Ejemplo 2: Un callback de Promise es una microtarea

El callback .then() de una Promise resuelta no se ejecuta de forma inline — se encola como microtarea y se ejecuta una vez que el código síncrono actual termina.

javascript— editable

Explicación: El resultado es Before the promise, After the promise, y luego Promise resolved (microtask). Aunque la Promise ya está resuelta, su callback .then() espera en la cola de microtareas hasta que el código síncrono termine — y entonces se ejecuta antes que cualquier timer.

Más Sobre la Prioridad de Micro y Macrotareas

Las microtareas siempre tienen mayor prioridad que las macrotareas. Después de que el script actual termine, el motor vacía todas las microtareas pendientes antes de tocar una sola macrotarea — incluso un setTimeout(..., 0) programado primero. Observa en el ejemplo siguiente que la Promise 2 encadenada, creada dentro de una microtarea, aún se ejecuta antes que cualquiera de los timers, porque la cola de microtareas se vacía completamente antes de que el bucle continúe.

javascript— editable

Resultado esperado:

Start
End
Promise 1
Promise 2
Timeout 1
Timeout 2

Esto muestra que las microtareas se ejecutan inmediatamente después del código síncrono, incluso antes que los timers programados para el mismo momento. La priorización significa que las actualizaciones basadas en Promises se resuelven lo antes posible.

Un Error Común: Inanición de Microtareas

Dado que el motor vacía la cola de microtareas completa antes de la siguiente macrotarea o de un renderizado, una microtarea que sigue programando más microtareas puede bloquear todo lo demás — los timers nunca se disparan y la página no puede volver a pintarse. Esto se denomina inanición de microtareas:

javascript— editable

Las cinco microtareas se ejecutan antes del callback de setTimeout, aunque el timer fue programado primero. En una aplicación real, una versión ilimitada de este bucle congelaría la interfaz. La solución es dividir el trabajo de larga duración en macrotareas (por ejemplo, setTimeout(..., 0)), lo que permite al bucle de eventos renderizar y gestionar eventos entre fragmentos.

Cuándo Usar Cada Una

  • Usa microtareas (Promises, queueMicrotask) cuando quieras que el código se ejecute tan pronto como termine la operación actual pero de forma asíncrona — como reaccionar a datos justo después de que se resuelva un fetch.
  • Usa macrotareas (setTimeout, dividir trabajo entre timers) cuando quieras deliberadamente ceder el control al navegador para que pueda renderizar o gestionar entradas antes de continuar — por ejemplo, fragmentar un cálculo pesado para que la página siga siendo responsiva.

Conclusión

El bucle de eventos ejecuta tu código síncrono hasta completarlo, luego vacía todas las microtareas, luego toma una macrotarea, y repite. Las microtareas (callbacks de Promise, queueMicrotask) siempre se ejecutan antes que la siguiente macrotarea (timers, eventos, I/O). Interiorizar esa única regla te permite predecir el orden exacto de cualquier código asíncrono.

Para profundizar, continúa con Promises, encadenamiento de Promises, async/await y el capítulo dedicado a las microtareas. Para las APIs de timer usadas aquí, consulta programación con setTimeout y setInterval.

Práctica

Práctica
En JavaScript, ¿qué ocurre cuando una Promise se resuelve y hay un manejador `.then()` adjunto?
En JavaScript, ¿qué ocurre cuando una Promise se resuelve y hay un manejador `.then()` adjunto?
Was this page helpful?