Command Palette

Search for a command to run...

Task Queue y Microtask Queue: Diferencias y Prioridad en JavaScript

Domina las diferencias entre la cola de tareas y la cola de microtareas, y cómo el Event Loop procesa cada una con diferentes prioridades.

Lectura: 12 min
Nivel: Intermedio

TL;DR - Resumen rápido

  • JavaScript tiene dos tipos de colas: Task Queue (macrotasks) y Microtask Queue
  • Las microtasks tienen mayor prioridad que las macrotasks
  • El Event Loop vacía completamente la Microtask Queue antes de procesar macrotasks
  • Promesas, queueMicrotask y MutationObserver generan microtasks
  • setTimeout, setInterval y eventos DOM generan macrotasks

Introducción a las Colas de Tareas

En el modelo de concurrencia de JavaScript, las tareas asíncronas se organizan en dos colas principales: la Task Queue (también llamada Macrotask Queue) y la Microtask Queue. Entender la diferencia entre estas dos colas es fundamental para predecir el orden de ejecución de tu código y escribir aplicaciones asíncronas eficientes.

Aunque ambas colas almacenan callbacks que deben ejecutarse después de que el código síncrono termine, tienen prioridades diferentes y se procesan en momentos distintos del ciclo del Event Loop. Esta distinción es crucial para evitar bugs sutiles y optimizar el rendimiento de tus aplicaciones.

¿Por qué dos colas?

Las microtasks se crearon para garantizar que ciertas operaciones críticas (como actualizaciones del DOM después de una promesa) se ejecuten lo antes posible. Esto asegura que la interfaz de usuario se mantenga consistente y que las operaciones asíncronas se completen antes de que el navegador renderice el próximo frame.

Task Queue (Macrotask Queue)

La Task Queue, también conocida como Macrotask Queue, es la cola tradicional de tareas asíncronas en JavaScript. Aquí se almacenan los callbacks de operaciones como setTimeout, setInterval, eventos del DOM, I/O y peticiones de red. El Event Loop procesa una macrotask a la vez, y después de cada macrotask, verifica si hay microtasks pendientes.

Fuentes de Macrotasks

Las macrotasks provienen de varias fuentes en JavaScript y las Web APIs del navegador. Cada una de estas fuentes tiene características diferentes en términos de timing y comportamiento, pero todas comparten la misma cola de ejecución.

fuentes-macrotasks.js
Loading code...

Este ejemplo muestra las fuentes más comunes de macrotasks. setTimeout ysetInterval son los más conocidos, programando callbacks para ejecutarse después de un tiempo específico. Los eventos del DOM (click, keydown, submit) también generan macrotasks cuando ocurren. Es importante notar que fetch, aunque es asíncrono, retorna una promesa y por tanto sus callbacks .then() son microtasks, no macrotasks.

  • <strong>setTimeout/setInterval</strong>: Ejecutan callbacks después de un tiempo específico
  • <strong>Eventos DOM</strong>: click, keydown, submit y otros eventos del navegador
  • <strong>requestAnimationFrame</strong>: Callbacks para animaciones sincronizadas con el render
  • <strong>I/O callbacks</strong>: Operaciones de lectura/escritura de archivos en Node.js
  • <strong>setImmediate</strong>: (Solo Node.js) Ejecuta callbacks en el siguiente ciclo del Event Loop

Microtask Queue

La Microtask Queue es una cola especial con mayor prioridad que la Task Queue. Aquí se almacenan las microtasks, que son tareas que deben ejecutarse lo antes posible después de que el código síncrono termine. El Event Loop procesa TODAS las microtasks en la cola antes de pasar a la siguiente macrotask.

Esta prioridad superior es fundamental para garantizar la consistencia del estado de la aplicación. Por ejemplo, cuando una promesa se resuelve, su then() se ejecuta inmediatamente en la siguiente microtask, asegurando que cualquier código que dependa de esa resolución se ejecute antes de que ocurra cualquier otra operación asíncrona.

Fuentes de Microtasks

Las microtasks provienen de fuentes más específicas que las macrotasks. Las promesas son la fuente más común, pero también existen otras como queueMicrotask y MutationObserver. Cada fuente tiene su propio caso de uso, pero todas comparten la misma cola de ejecución de alta prioridad.

fuentes-microtasks.js
Loading code...

Este ejemplo muestra las tres fuentes principales de microtasks en JavaScript.Promise.resolve() y Promise.reject() son las más utilizadas, creando microtasks cuando se llaman sus callbacks .then(), .catch()o .finally(). La API queueMicrotask() permite crear microtasks explícitamente sin usar promesas. MutationObserver genera microtasks cuando detecta cambios en el DOM, útil para reaccionar a modificaciones del árbol DOM.

Microtask vs Macrotask

Las microtasks se ejecutan inmediatamente después del código síncrono, mientras que las macrotasks esperan a que la Microtask Queue esté vacía. Esto significa que las promesas siempre se ejecutan antes que los callbacks de setTimeout, incluso si ambos tienen el mismo delay.

Prioridad de Ejecución

La prioridad de ejecución entre microtasks y macrotasks es uno de los conceptos más importantes de entender en JavaScript. El Event Loop sigue un algoritmo específico que garantiza que las microtasks siempre se procesen antes de las macrotasks, lo que tiene implicaciones directas en el orden de ejecución de tu código.

Algoritmo del Event Loop

El Event Loop sigue este algoritmo en cada iteración: primero, ejecuta una macrotask de la Task Queue. Segundo, ejecuta todas las microtasks disponibles en la Microtask Queue. Tercero, renderiza la UI si es necesario. Cuarto, repite el proceso. Este ciclo garantiza que las microtasks siempre se ejecuten antes de la siguiente macrotask.

prioridad-ejecucion.js
Loading code...

Este ejemplo demuestra claramente la prioridad de las microtasks. Aunque setTimeout tiene un delay de 0ms, su callback se ejecuta después de todas las microtasks. El código síncrono se ejecuta primero, luego TODAS las microtasks, y finalmente la macrotask. Este comportamiento es consistente y predecible.

Advertencia de Starvation

Si creas microtasks dentro de microtasks (como en una promesa dentro de un then), el Event Loop seguirá procesándolas antes de pasar a la siguiente macrotask. Esto puede causar "starvation" de las macrotasks, donde los callbacks de setTimeout nunca se ejecutan si las microtasks se siguen generando infinitamente.

Errores Comunes con Colas de Tareas

Los siguientes errores son comunes y pueden causar bugs sutiles en aplicaciones en producción. Estos problemas suelen manifestarse como código que se ejecuta en orden inesperado o aplicaciones que dejan de responder.

Error 1: Mezclar Promesas y setTimeout Incorrectamente

Asumir que setTimeout y las promesas tienen la misma prioridad de ejecución es un error común que causa bugs de timing. Si tu código depende de que unsetTimeout se ejecute antes que una promesa (o viceversa), el comportamiento será impredecible a menos que entiendas la diferencia entre macrotasks y microtasks.

error-mix-promesas-settimeout.js
Loading code...

Este ejemplo muestra el error común de asumir un orden incorrecto. El código en la promesa asume incorrectamente que el setTimeout ya se ejecutó, pero esto nunca ocurre porque las promesas (microtasks) siempre tienen mayor prioridad. La solución mostrada al final del archivo demuestra cómo encadenar correctamente: envuelve elsetTimeout en una promesa para mantener el control del orden de ejecución.

Error 2: Cadenas Largas de Microtasks

Crear cadenas largas de microtasks donde cada una genera otra puede causar "starvation" de las macrotasks. Aunque el ejemplo usa un límite de 10 iteraciones, en código real esto puede ocurrir sin límite (por ejemplo, en procesamiento recursivo con promesas), bloqueando completamente el Event Loop e impidiendo que el navegador renderice o responda a eventos del usuario.

error-microtasks-infinitas.js
Loading code...

Este ejemplo demuestra el problema: las 10 microtasks se ejecutan consecutivamente antes de que el Event Loop pueda procesar cualquier macrotask. Si esto fuera infinito (sin el límite de 10), el setTimeout nunca se ejecutaría. La solución mostrada usa setTimeout(fn, 0) en lugar de promesas, lo que convierte cada iteración en una macrotask, permitiendo que el navegador renderice y procese eventos entre iteraciones.

Prevención de Starvation

Si necesitas ejecutar muchas operaciones asíncronas consecutivas, usasetTimeout(fn, 0) en lugar de promesas encadenadas. Esto convierte cada operación en una macrotask, permitiendo que el Event Loop procese eventos del usuario y renderice la UI entre iteraciones. Para operaciones realmente pesadas, considera usar Web Workers.

Resumen: Task Queue y Microtask Queue

Conceptos principales:

  • JavaScript tiene dos colas: Task Queue (macrotasks) y Microtask Queue
  • Las microtasks tienen mayor prioridad que las macrotasks
  • El Event Loop procesa todas las microtasks antes de la siguiente macrotask
  • Promesas, queueMicrotask y MutationObserver generan microtasks
  • setTimeout, setInterval, eventos DOM y fetch generan macrotasks

Mejores prácticas:

  • Usa promesas para operaciones que deben completarse antes de renderizar
  • Usa setTimeout para operaciones que pueden esperar al siguiente ciclo
  • Evita cadenas largas de microtasks que bloqueen macrotasks
  • Entiende que fetch retorna promesas (microtasks), no es una macrotask
  • Para procesamiento pesado recursivo, usa setTimeout en lugar de promesas