Explicación del bucle de eventos de JavaScript: pila de llamadas, microtareas y temporizadores con ejemplos

  1. Inicio
  2. Blog
  3. Desarrollo de software

Desarrollo de software

Explicación del bucle de eventos de JavaScript: Pila de llamadas, microtareas y temporizadores con ejemplos

¿Por qué un setTimeout de cero sigue ejecutándose al final? ¿Cómo puede un hilo manejar tantas cosas a la vez? Aprenda cómo funciona realmente el bucle de eventos, pruébelo con el código que ejecutamos y resuelva la pregunta clásica de la entrevista.

9 min de lectura
Explicación del bucle de eventos de JavaScript: pila de llamadas, microtareas y temporizadores con ejemplos

Puntos clave

  • JavaScript ejecuta su código en un un solo hilo, una pieza a la vez.
  • Cuando la pila de llamadas está vacía, todas las microtareas se ejecutan primero y luego una tarea.
  • setTimeout(fn, 0) significa “tan pronto como sea posible después de un retraso mínimo”, no “ahora”.
  • El trabajo sincrónico prolongado bloquea todo, así que divídalo o muévalo a un trabajador.

Ejecute algunas líneas con setTimeout y una promesa, y el orden de salida puede sorprenderlo. El bucle de eventos de JavaScript explica por qué. Es el mecanismo que permite que un lenguaje de subproceso único maneje temporizadores, llamadas de red y clics sin congelarse.

Una vez que lo comprendes, toda una clase de errores confusos empiezan a tener sentido. Ejecutamos cada ejemplo en Node.js 24 y el resultado proviene de esa ejecución. Las reglas básicas de ordenamiento también se aplican en los navegadores, y señalamos en qué se diferencia Node.js. Obtendrá más de esto si ya conoce los conceptos básicos de las promesas de JavaScript.

Por qué existe el bucle de eventos

JavaScript ejecuta su código en un hilo principal, por lo que solo se ejecuta una parte a la vez. Si simplemente esperara una solicitud de red, una página se congelaría hasta que llegara la respuesta.

Entonces JavaScript no espera. El trabajo lento se entrega al entorno host, es decir, al navegador o Node.js, que puede manejarlo en segundo plano. Cuando finaliza ese trabajo, se coloca una devolución de llamada en una cola. El trabajo del bucle de eventos es mover las devoluciones de llamadas en cola a la pila cuando esté libre.

Piense en un chef trabajando solo. El chef mete un plato en el horno y pone un cronómetro, en lugar de mirarlo fijamente. Cuando suena el cronómetro, la tarea pasa a una lista y el chef la retoma después de terminar lo que tiene actualmente entre manos.

las partes moviles

parte que hace Ejemplos
Pila de llamadas Realiza un seguimiento de las funciones que se están ejecutando actualmente Tu código sincrónico
API de host Trabaja fuera de tu código Temporizadores, fetch, lecturas de archivos
cola de tareas Retiene las devoluciones de llamada que están listas para ejecutarse setTimeout, clics, E/S
Cola de microtareas Mantiene devoluciones de llamada de mayor prioridad .then, await, queueMicrotask
Bucle de eventos Decide qué se ejecuta a continuación Funciona continuamente

Las tareas a veces se denominan macrotareas. Estrictamente hablando, puede haber varios orígenes de tareas y los detalles exactos difieren entre entornos.

las reglas

  1. Ejecute todo el código sincrónico hasta que la pila de llamadas esté vacía.
  2. Ejecute todas las microtareas, incluidas las nuevas que se agreguen a lo largo del camino.
  3. En un navegador, genere una actualización si es necesario.
  4. Tome una tarea de una cola y ejecútela.
  5. Vuelva al paso 2.

El detalle más importante: todas microtareas, luego una tarea. La mayoría de los acertijos de bucles de eventos se resuelven aplicando esa única regla.

ver el pedido

console.log("1 sync");
setTimeout(() => console.log("5 timeout"), 0);
Promise.resolve().then(() => console.log("3 promise"));
queueMicrotask(() => console.log("4 microtask"));
console.log("2 sync");

// 1 sync
// 2 sync
// 3 promise
// 4 microtask
// 5 timeout

Esto es lo que pasó. Las dos líneas console.log son ​​sincrónicas, por lo que se ejecutaron primero. La devolución de llamada de promesa y queueMicrotask entraron en la cola de microtareas y se ejecutaron tan pronto como la pila estuvo vacía, en el orden en que se agregaron. La devolución de llamada del temporizador tuvo que esperar un turno de tarea, aunque su retraso era cero.

Las microtareas se ejecutan entre temporizadores

Después de cada tarea, la cola de microtareas se vacía nuevamente. Este ejemplo lo demuestra:

setTimeout(() => {
  console.log("timer 1");
  Promise.resolve().then(() => console.log("promise inside timer 1"));
}, 0);
setTimeout(() => console.log("timer 2"), 0);

// timer 1
// promise inside timer 1
// timer 2

La promesa, que se creó durante el primer cronómetro, se ejecutó antes del segundo cronómetro. Los navegadores modernos y Node.js 11 y posteriores se comportan de esta manera.

donde cabe la espera

await divide una función en dos. Todo lo anterior se ejecuta de forma sincrónica y todo lo posterior se programa como una microtarea. Esto sorprende a mucha gente:

async function task() {
  console.log("A before await");
  await null;
  console.log("C after await");
}

task();
console.log("B after calling task");

// A before await
// B after calling task
// C after await

Una función async no se inicia en segundo plano. Se ejecuta en el mismo hilo hasta llegar al primero await. Esto se aplica incluso cuando se espera algo que no es una promesa real, como null. Consulte nuestra guía JavaScript async/await para obtener más información sobre esta sintaxis.

Bloqueando el bucle

Mientras se ejecuta el código sincrónico, nada más puede hacerlo. Para mostrar esto, configuramos un temporizador de 0 ms y luego mantuvimos el hilo ocupado durante medio segundo:

const start = Date.now();
setTimeout(() => console.log("timer ran after", Date.now() - start, "ms"), 0);
while (Date.now() - start < 500) {}

// timer ran after about 500 ms

El retraso del temporizador es un mínimo, no una garantía. Se ejecutó sólo cuando la pila quedó libre. En una página web, un bucle largo como este significa que los clics no responden y las animaciones se detienen. En un servidor, todas las demás solicitudes tienen que esperar, por lo que una llamada síncrona lenta puede perjudicar a todos los usuarios de un proceso Node.js.

Las causas comunes de bloqueo incluyen bucles enormes, análisis de JSON muy grandes, funciones de archivos sincrónicos como readFileSync en controladores de solicitudes y expresiones regulares catastróficas.

Consejo: En Chrome DevTools, el panel Rendimiento marca las tareas que duran más de 50 ms como tareas largas. Es una forma rápida de encontrar qué hace que una página parezca lenta.

Mantenerse receptivo

Para trabajos pesados, déle al bucle de eventos la oportunidad de respirar. Una forma es hacer una pausa en un temporizador de vez en cuando, lo que permite ejecutar otras tareas:

const items = [0, 1, 2, 3, 4];

async function processAll() {
  for (const item of items) {
    if (item === 2) await new Promise((r) => setTimeout(r, 0));
    console.log("item", item);
  }
}

setTimeout(() => console.log("other work gets a turn"), 0);
processAll();

// item 0
// item 1
// other work gets a turn
// item 2
// item 3
// item 4

El otro cronómetro corrió a la mitad, porque cedimos con una tarea real. El código real produciría cada cierto número de elementos o milisegundos. Para trabajos que realmente requieren mucha CPU, como el procesamiento de imágenes, utilice un Web Worker en los navegadores o un subproceso de trabajo en Node.js, que se ejecutan en subprocesos separados.

Hambre de microtareas

Dado que el bucle vacía completamente la cola de microtareas antes de continuar, una microtarea que sigue programando más puede detener todo lo demás. Probamos esto con 100.000 microtareas encadenadas:

let count = 0;
let timerFired = false;
setTimeout(() => { timerFired = true; }, 0);

function spin() {
  count++;
  if (count < 100000) queueMicrotask(spin);
  else console.log("timer fired during microtasks?", timerFired);
}
queueMicrotask(spin);

// timer fired during microtasks? false

El cronómetro no pudo correr hasta que todos terminaron. Si esa cadena nunca terminara, los temporizadores nunca se activarían y, en un navegador, la página nunca se volvería a pintar. Tenga en cuenta que ceder con await en una promesa resuelta no ayuda aquí, porque solo agrega otra microtarea. Necesitas una tarea real, como un cronómetro, para darle un turno a los demás.

Representación en navegadores

Un navegador normalmente tiene la oportunidad de pintar entre tareas. Eso explica un error común:

spinner.hidden = false;
doHeavyWork();              // blocks the thread
spinner.hidden = true;

La ruleta nunca aparece porque el navegador no tuvo oportunidad de renderizar entre esas líneas. Para solucionarlo, deje pasar un marco antes de que comience el trabajo pesado, o traslade ese trabajo a un trabajador. Para el código que cambia elementos visuales, requestAnimationFrame se ejecuta justo antes de la siguiente pintura, lo que la convierte en la herramienta adecuada para la animación.

Diferencias de Node.js

Node.js tiene su propio bucle de eventos con varias fases, incluidos temporizadores, sondeo de E/S y setImmediate. También tiene process.nextTick, que se ejecuta en su propia cola. Esto es lo que vimos al ejecutarlos desde dentro de una función asíncrona:

setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("promise"));

// promise
// nextTick
// immediate
// timeout

Muchos artículos afirman que nextTick siempre supera las promesas y que un cronómetro supera a setImmediate. Nuestro resultado muestra que no es tan simple. Nuestro código ya se estaba ejecutando dentro de una microtarea, por lo que la cola de promesa continuó primero. Y se sabe que el orden de setTimeout y setImmediate varía según desde dónde se llaman. Así que no escribas código que dependa del orden exacto de estos. Si una cosa debe suceder tras otra, encadénelas explícitamente.

cuestionario de entrevista

Intente calcular el orden antes de leer la respuesta:

console.log("a");
setTimeout(() => console.log("b"), 0);
Promise.resolve().then(() => {
  console.log("c");
  setTimeout(() => console.log("d"), 0);
  Promise.resolve().then(() => console.log("e"));
});
console.log("f");

Nuestra ejecución imprimió a, f, c, e, b, d. He aquí por qué:

  1. a y f son sincrónicos, por lo que se ejecutan primero.
  2. La pila está vacía, por lo que se ejecutan las microtareas: c, luego la nueva microtarea e, en la misma ronda.
  3. Ahora una tarea: el temporizador b, que se puso en cola primero.
  4. Luego, la siguiente tarea: el temporizador d, que se puso en cola durante la microtarea.

Errores comunes

  • Pensar setTimeout es exacto. El retraso es sólo mínimo.
  • Pensar async significa otro hilo. El código asíncrono todavía se ejecuta en el hilo principal entre esperas.
  • Usar dormir para arreglar el orden. Esperar a la condición real es mucho más confiable, lo que también se aplica a las pruebas inestables.
  • Usando forEach con await. No espera entre elementos. Consulte Métodos de matriz de JavaScript.
  • Realizar un trabajo pesado sincrónico en un controlador de servidor. Una solicitud puede retrasar todas las demás solicitudes.

Conclusión

Recuerde la regla básica: ejecute la pila hasta que esté vacía, ejecute todas las microtareas, luego ejecute una tarea y repita. Todo lo demás se deriva de ello. Para que se mantenga, escriba su propia versión del cuestionario anterior, prediga el resultado en papel y luego ejecútelo. Equivocarse y comprender por qué le enseñará más que cualquier diagrama.

Preguntas frecuentes

¿Qué es el bucle de eventos en JavaScript?

Es el mecanismo que ejecuta devoluciones de llamadas en cola cuando la pila de llamadas está vacía. Permite que JavaScript de un solo subproceso maneje temporizadores, entradas y respuestas de red sin bloquear.

¿Cuál es la diferencia entre una microtarea y una tarea?

Las microtareas, como las devoluciones de llamadas de promesa, se ejecutan tan pronto como se vacía la pila. Las tareas, como los temporizadores, se ejecutan una a la vez y las microtareas se agotan después de cada una.

¿Por qué setTimeout con 0 no se ejecuta inmediatamente?

Su devolución de llamada debe esperar un turno de tarea. Todo el código síncrono y las microtareas se ejecutan primero y el retraso es mínimo en lugar de exacto.

¿JavaScript es de un solo subproceso?

Su código JavaScript principal se ejecuta en un hilo. Los navegadores y Node.js utilizan otros subprocesos para algunos trabajos en segundo plano y los trabajadores le permiten ejecutar código en paralelo.

¿Await bloquea el bucle de eventos?

No. await pausa solo esa función asíncrona y, mientras tanto, se puede ejecutar otro código. Sin embargo, un código sincrónico largo antes o después aún puede bloquear el bucle.

sigue aprendiendo

Trabajar con Stack Quora

¿Necesitas ayuda con pruebas, WordPress o SEO?

Ayudamos a los equipos a enviar mejor software y hacer crecer sus sitios. Cuéntenos en qué está trabajando y le sugeriremos un siguiente paso claro.

Sigue leyendo

Más guías sobre testing, IA y desarrollo moderno

¿Te gustó este artículo? Elige un tema y salta directo a nuestros tutoriales más recientes.

Inteligencia artificial

Explicación de las alucinaciones de la IA: por qué los modelos de lenguaje inventan cosas y cómo reducirlas

Se explicaron las alucinaciones de la IA: qué son, por qué los modelos de lenguaje las producen, tipos comunes, cómo reducirlas con conexión a tierr…

Leer artículo →
DevOps

Explicación de las variables de entorno: archivos .env, secretos y configuración bien hechos

Variables de entorno explicadas con ejemplos de Python que ejecutamos: leerlas y configurarlas, por qué cada valor es una cadena, archivos .env, her…

Leer artículo →
Desarrollo de software

Explicación de la limitación de velocidad de API: algoritmos, encabezados y manejo de errores 429

La limitación de la tasa de API se explica con una simulación que ejecutamos: depósito de tokens, ventana fija, ventana deslizante, 429 respuestas, …

Leer artículo →
Automatización de pruebas

Burlarse en Python: unittest.mock, patch y los errores que hacen que las pruebas mientan

Burlarse en Python con unittest.mock, probado: Mock y return_value, side_effect, parches donde se usa un nombre, especificación y autospec, fechas d…

Leer artículo →
Pruebas de software

Explicación de la cobertura del código: cobertura de línea versus sucursal y lo que realmente significa 100%

Cobertura del código explicada con una ejecución real de cobertura.py: cobertura de línea, rama y condición, lectura de un informe, por qué una cobe…

Leer artículo →
Bases de datos y SQL

DELETE vs TRUNCATE vs DROP en SQL: diferencias, reversión y cuándo usar cada uno

DELETE vs TRUNCATE vs DROP explicado con SQL probado: qué elimina cada uno, reversión, activadores, restablecimiento de identidad, velocidad y en qu…

Leer artículo →
Bases de datos y SQL

Vistas SQL explicadas: cree, use y evite los errores, con ejemplos

Vistas SQL explicadas con consultas que ejecutamos: creación de una vista, resultados en vivo, ocultación de columnas, rendimiento, vistas actualiza…

Leer artículo →
Desarrollo de software

Interfaz TypeScript versus tipo: diferencias reales, probadas con el compilador

Interfaz TypeScript versus tipo, verificado con el compilador real: qué pueden hacer ambos, qué puede hacer solo uno, fusión de declaraciones, exten…

Leer artículo →
Desarrollo de software

Python *args y **kwargs explicados: funciones flexibles con ejemplos claros

Python *args y **kwargs explicados con el código que ejecutamos: empaquetado y desempaquetado, orden de parámetros, argumentos solo de palabras clav…

Leer artículo →
Desarrollo de software

Funciones Lambda de Python: sintaxis, usos reales y cuándo no utilizarlas

Funciones lambda de Python explicadas con el código que ejecutamos: sintaxis, clasificación con clave, mapa y filtro, cierres, trampa de enlace tard…

Leer artículo →
Inteligencia artificial

¿Qué son los agentes de IA? Cómo funcionan, con un bucle de agente simple que puedes ejecutar

¿Qué son los agentes de IA? Una guía sencilla sobre el bucle del agente, las herramientas, la memoria y las barreras de seguridad, con un pequeño bu…

Leer artículo →
DevOps

Monitoreo versus observabilidad: la verdadera diferencia, con registros, métricas y seguimientos

Explicación del monitoreo versus observabilidad: fallas conocidas y desconocidas, registros, métricas y seguimientos, un recorrido por incidentes de…

Leer artículo →
DevOps

Docker Volumes versus Bind Mounts: persistir los datos del contenedor de la manera correcta

Se explican los volúmenes de Docker frente a los montajes de enlace: por qué desaparecen los datos del contenedor, volúmenes con nombre, montajes de…

Leer artículo →
Automatización de pruebas

TDD vs BDD: diferencias clave, una función creada en ambos sentidos y cuándo usar cada una

TDD vs BDD explicado con una característica de descuento construida en ambos sentidos: refactor rojo-verde con el código que ejecutamos, escenarios …

Leer artículo →
Pruebas de software

Pruebas de Aceptación de Usuario (UAT): Proceso, Lista de Verificación y Ejemplos Reales

Explicación de las pruebas de aceptación del usuario: quién las ejecuta, en qué se diferencia del control de calidad, un proceso de siete pasos, una…

Leer artículo →
Bases de datos y SQL

Valores NULL de SQL explicados: IS NULL, COALESCE, NULLIF y las trampas que se deben evitar

Los valores NULL de SQL se explican con las consultas que ejecutamos: lógica de tres valores, IS NULL, COALESCE, NULLIF, cómo los agregados, las uni…

Leer artículo →
Bases de datos y SQL

Declaración SQL CASE WHEN: sintaxis, ejemplos reales y errores comunes

CASO SQL CUANDO se explicó con las consultas que ejecutamos: formularios simples y buscados, orden de condiciones, pivotes con SUMA, trucos COUNT, c…

Leer artículo →
Desarrollo de software

¿Qué es JWT? Tokens web JSON explicados con un ejemplo práctico

¿Qué es JWT? JSON Web Tokens explicó: las tres partes, un token creado y manipulado en el código que ejecutamos, JWT vs sesiones, almacenamiento, to…

Leer artículo →
Desarrollo de software

Explicación del bucle de eventos de JavaScript: pila de llamadas, microtareas y temporizadores con ejemplos

El bucle de eventos de JavaScript explicado con el código que ejecutamos: pila de llamadas, colas de tareas y microtareas, por qué setTimeout 0 no e…

Leer artículo →
Desarrollo de software

Explicación de los generadores y el rendimiento de Python: ahorre memoria con ejemplos reales

Los generadores de Python y el rendimiento se explican con el código que ejecutamos: cómo se pausan y reanudan, midieron el ahorro de memoria, canal…

Leer artículo →
Inteligencia artificial

¿Qué son las incrustaciones? Los vectores y la similitud del coseno se explican de forma sencilla

¿Qué son las incrustaciones? Una guía en lenguaje sencillo sobre vectores, similitud de cosenos y búsqueda semántica, con un pequeño ejemplo que eje…

Leer artículo →
DevOps

Implementación azul-verde versus implementación canaria: diferencias, compensaciones y cómo elegir

Explicación de la implementación azul-verde versus canaria: cómo funciona cada uno, costo, riesgo y reversión, agrupación estable de usuarios que pr…

Leer artículo →
Automatización de pruebas

Pruebas inestables: qué las causa y cómo solucionarlas para siempre

Pruebas inestables explicadas con demostraciones que realizamos: las causas principales, cómo confirmar y diagnosticar una, correcciones adecuadas p…

Leer artículo →
Pruebas de software

Pruebas de carga frente a pruebas de estrés: diferencias, métricas y ejemplos de k6

Explicación de las pruebas de carga frente a las pruebas de estrés: qué prueba cada una, pruebas de picos y de absorción, las métricas que importan,…

Leer artículo →
Pruebas de software

Caso de prueba frente a escenario de prueba: diferencias clave, ejemplos y cuándo utilizar cada uno

Caso de prueba versus escenario de prueba explicado: definiciones claras, una tabla en paralelo, una función de restablecimiento de contraseña tomad…

Leer artículo →
Bases de datos y SQL

Optimización de consultas SQL: lea planes de ejecución y corrija consultas lentas, con tiempos

Optimización de consultas SQL probada en un millón de filas: lea planes de ejecución, agregue los índices correctos, corrija filtros no sargables, u…

Leer artículo →
Bases de datos y SQL

SQL UNION vs UNION ALL: diferencias, ejemplos y rendimiento probado

SQL UNION vs UNION ALL explicado con consultas que ejecutamos: duplicados, las reglas que siguen ambos, un error de datos real, rendimiento medido, …

Leer artículo →
Desarrollo de software

Tutorial de JavaScript Fetch API: GET, POST, manejo de errores y tiempos de espera

Un tutorial de JavaScript Fetch API probado en un servidor local: GET y POST, por qué 404 no arroja, un asistente reutilizable, cadenas de consulta,…

Leer artículo →
Desarrollo de software

Manejo de archivos Python: lectura, escritura, CSV, JSON y pathlib con ejemplos

El manejo de archivos Python se explica con el código que ejecutamos: modos abiertos, lectura de archivos grandes, CSV, JSON, pathlib, archivos bina…

Leer artículo →
Desarrollo de software

Clases y objetos de Python: tutorial de programación orientada a objetos con ejemplos reales

Clases y objetos de Python explicados con el código que ejecutamos: __init__, métodos, atributos de clase versus instancia, herencia, propiedades, m…

Leer artículo →
Inteligencia artificial

¿Qué es un modelo de lenguaje grande? Cómo funcionan los LLM, explicados sin exageraciones

¿Qué es un modelo de lenguaje grande? Los LLM explicados en lenguaje sencillo: tokens, incrustaciones, atención, entrenamiento, ventanas de temperat…

Leer artículo →
DevOps

Comandos de Linux para principiantes: la guía esencial de terminal con ejemplos

Comandos de Linux para principiantes, agrupados por tarea: navegación, archivos, visualización de registros, grep, canalizaciones, permisos, proceso…

Leer artículo →
Automatización de pruebas

Selenium vs Playwright: diferencias clave, comparación de códigos y cuál elegir

Comparación de Selenium vs Playwright: arquitectura, espera, soporte de navegador e idioma, depuración, la misma prueba en ambas herramientas, conse…

Leer artículo →
Pruebas de software

Partición de equivalencia: técnica de diseño de pruebas explicada con ejemplos resueltos

Explicación de la partición de equivalencia: cómo encontrar particiones válidas e inválidas, ejemplos prácticos para edades, contraseñas y cupones, …

Leer artículo →
Pruebas de software

Severidad vs Prioridad en Pruebas de Software: Diferencias, Matriz y Ejemplos Reales

Explicación de gravedad versus prioridad en las pruebas de software: definiciones claras, una matriz de cuatro vías con ejemplos, escalas de niveles…

Leer artículo →
Bases de datos y SQL

Tutorial de subconsultas SQL: tipos, subconsultas correlacionadas y EXISTENCIAS con ejemplos

Un tutorial de subconsultas SQL con consultas que ejecutamos: subconsultas escalares, IN, FROM y SELECT, subconsultas correlacionadas, EXISTS, la tr…

Leer artículo →
Bases de datos y SQL

Transacciones SQL y ACID explicados: COMMIT, ROLLBACK y niveles de aislamiento

Transacciones SQL y ACID explicadas con una transferencia bancaria que ejecutamos: COMMIT, ROLLBACK, puntos de guardado, bloqueos, niveles de aislam…

Leer artículo →
Desarrollo de software

Git merge vs rebase: diferencias, ejemplos y cuándo usar cada uno

Git merge vs rebase explicado con salida real del log, una tabla comparativa, la regla de oro del rebase y un flujo de trabajo seguro para equipos.

Leer artículo →
Desarrollo de software

List comprehension en Python: sintaxis, ejemplos y cuándo evitarla

List comprehension en Python explicada: sintaxis, filtros, if-else, bucles anidados, tiempos medidos y cuándo es mejor un bucle for normal.

Leer artículo →