Node.js Internals

Node.js Event Loop

Node ejecuta tu JavaScript en un único hilo, pero es capaz de manejar miles de conexiones. El event loop y sus fases son la razón de que esto funcione — y por qué bloquearlo es perjudicial.

intermediate15 min readUpdated 15 sept 2026
order.js
js
// order.js
console.log("1: synchronous");

setTimeout(() => console.log("5: timers phase"), 0);
setImmediate(() => console.log("6: check phase"));

Promise.resolve().then(() => console.log("3: microtask"));

process.nextTick(() => console.log("2: nextTick"));
Hilos
Un hilo principal
Motor
V8 más libuv
Fases
Timers, poll, check, close
Microtasks
nextTick y promises
Thread pool
Tamaño predeterminado de 4
Bloqueo
Detiene todo

Por que importa

Por qué es importante el event loop

Un hilo, muchas conexiones

El loop multiplexa miles de sockets en un solo hilo al evitar esperar de forma síncrona.

El orden es predecible

Conocer las fases y las colas te permite razonar sobre cuándo se ejecutan los callbacks.

El bloqueo es el enemigo

Cualquier tarea síncrona prolongada congela todas las demás solicitudes, por eso el trabajo pesado se desplaza fuera del loop.

La imagen completa

Las tres partes del loop

Las fases deciden qué se ejecuta y cuándo, las microtasks se ejecutan entre fases, y el thread pool gestiona el trabajo que no puede ser no bloqueante.

Fases

Programación

El loop cicla a través de timers, callbacks pendientes, poll, check y callbacks de cierre.

Colas

Priorización

Las microtasks se vacían entre fases, y nextTick se ejecuta antes que los callbacks de las promises.

Thread pool

Descarga

El trabajo de archivos, DNS y crypto se ejecuta en el thread pool de libuv para que el loop permanezca libre.

El event loop de un vistazo

Ideas fundamentales

Fase de Timers

Ejecuta los callbacks programados por setTimeout y setInterval que ya han vencido.

Fase de Poll

Recupera nuevos eventos de I/O y ejecuta sus callbacks, bloqueándose cuando está inactivo.

Fase de Check

Ejecuta los callbacks de setImmediate inmediatamente después de la fase de poll.

Microtasks

process.nextTick y los callbacks de promises se ejecutan entre fases.

Thread pool

libuv ejecuta fs, DNS y algunas tareas de crypto en hilos de trabajo (worker threads).

Monitoreo

Vigila el retraso del loop y la utilización del event loop para detectar bloqueos.

Una breve historia

De servidores bloqueantes a I/O basado en eventos

  1. 2009

    Node basado en eventos

    Node combina V8 con libuv para ejecutar JavaScript con I/O no bloqueante.

    09
  2. 2011

    Extracción de libuv

    El event loop y las abstracciones de plataforma se convierten en una librería independiente.

    11
  3. 2015

    Promises y microtasks

    Las promises nativas añaden una nueva cola con reglas de orden claras.

    15
  4. 2020

    Mejores diagnósticos

    Herramientas como la utilización del event loop hacen que el bloqueo sea medible.

    20
  5. Hoy

    El mismo modelo en todas partes

    El event loop es la base de servidores, herramientas, runtimes serverless e incluso navegadores.

    Hoy

La guia completa

Node.js Event Loop: Todo lo que necesitas saber

Por qué es importante el event loop

Node.js ejecuta tu JavaScript en un único hilo principal, y aun así, un solo servidor puede gestionar miles de conexiones simultáneas. Esto es posible porque el event loop nunca se detiene a esperar. Cuando una operación bloquearía el hilo, Node la delega al sistema operativo o a un thread pool y continúa con otras tareas. Cuando el resultado está listo, se encola un callback y el loop lo ejecuta.

Comprender el loop explica gran parte del comportamiento de Node: por qué una lectura de archivo síncrona congela todo el servidor, por qué una promesa se registra en el log antes que un temporizador de retraso cero y por qué el trabajo intensivo de CPU requiere un worker. Es el modelo mental más importante para escribir código rápido en Node.

El loop y sus fases

El event loop es un ciclo de fases. Cada fase tiene una cola de callbacks, y el loop los procesa antes de continuar con la siguiente.

  1. Timers — callbacks programados mediante setTimeout y setInterval que ya han vencido.
  2. Pending callbacks — callbacks del sistema diferidos, como algunos errores de TCP.
  3. Idle, prepare — tareas internas de mantenimiento.
  4. Poll — recupera nuevos eventos de I/O y ejecuta sus callbacks. Si no hay nada listo, puede bloquearse aquí brevemente.
  5. Check — ejecuta callbacks de setImmediate.
  6. Close callbacks — gestiona eventos como el cierre de sockets.

El loop continúa ciclando mientras haya trabajo pendiente. Cuando no quedan timers, no hay I/O pendiente ni handles, el proceso finaliza.

Microtareas: nextTick y promises

Entre fases, y después de cada callback, Node vacía dos colas de alta prioridad:

  • Callbacks de process.nextTick, que se ejecutan primero.
  • Callbacks de Promise, que se ejecutan después de nextTick.

Ambas se vacían completamente antes de que el loop avance, lo que tiene dos consecuencias. Primero, se ejecutan antes que los timers y los callbacks de I/O programados previamente. Segundo, un nextTick recursivo o una cadena de promises infinita pueden bloquear el loop (starve the loop), evitando que el I/O llegue a ejecutarse.

// order.js
console.log("1: synchronous");

setTimeout(() => console.log("5: timers"), 0);
setImmediate(() => console.log("6: check"));

Promise.resolve().then(() => console.log("3: promise"));
process.nextTick(() => console.log("2: nextTick"));

La salida es 1, 2, 3, luego 5 y 6. El orden de 5 y 6 puede variar en el nivel superior porque depende de cuánto tiempo tome llegar a la fase de timers; dentro de un callback de I/O, setImmediate siempre se ejecuta antes que setTimeout.

El thread pool de libuv

Algunas operaciones no pueden realizarse de forma asíncrona por el SO de manera portable: el acceso al sistema de archivos, las consultas DNS y algunas funciones criptográficas. libuv ejecuta estas tareas en un thread pool, que por defecto tiene cuatro hilos y es configurable mediante UV_THREADPOOL_SIZE.

Es por esto que fs.readFile no bloquea tu código, aunque internamente no esté basado en eventos. También significa que una ráfaga de operaciones de archivos puede quedar en cola detrás de un pool de cuatro hilos. Las API async mantienen el hilo principal libre; el pool se encarga de la espera.

Bloqueando el event loop

Cualquier operación síncrona y lenta en el hilo principal bloquea todo: otras solicitudes, timers y E/S. Los culpables más comunes incluyen:

  • readFileSync y otras llamadas síncronas de archivos en los manejadores de solicitudes.
  • JSON.parse o JSON.stringify extensos en payloads grandes.
  • Criptografía síncrona, como pbkdf2Sync con un número elevado de iteraciones.
  • Bucles cerrados, backtracking de regex y transformaciones de datos pesadas.
  • Librerías bloqueantes que realizan trabajo de CPU en el hilo principal.

Mide en lugar de adivinar: el retraso del event loop (event loop delay) y la utilización del event loop (event loop utilization) te indican cuánto tiempo permanece bloqueado el loop. Un aumento en el retraso bajo carga es la señal clara.

Sacando el trabajo del loop

Cuando el trabajo depende genuinamente de la CPU (CPU-bound), muévelo:

  • worker_threads para computación dentro del mismo proceso, mediante el paso de mensajes.
  • child processes para tareas más pesadas o aisladas.
  • Un servicio independiente para las cargas de trabajo más pesadas, escalado de forma independiente.
  • Chunking: divide las tareas grandes en piezas más pequeñas y cede el control entre ellas.

Para I/O, utiliza las API async y streams para que el loop permanezca libre.

Mejores prácticas

  • Utiliza APIs async en los request handlers; nunca bloquees el loop.
  • Cede el control a I/O mediante setImmediate en lugar de nextTick recursivos.
  • Mueve las tareas intensivas de CPU a worker threads o procesos hijos.
  • Transmite datos grandes mediante streams en lugar de almacenarlos todos en memoria.
  • Ajusta UV_THREADPOOL_SIZE solo después de realizar mediciones.
  • Monitorea el retraso y la utilización del event loop en producción.
  • Mantén las cadenas de callbacks cortas para que las microtasks no bloqueen el I/O.

Errores comunes

  • Usar readFileSync dentro de un hot path.
  • process.nextTick recursivos que bloquean el loop.
  • Asumir que Node.js es completamente single-threaded e ignorar el thread pool.
  • Realizar cómputos pesados inline en lugar de delegarlos.
  • Esperar que setTimeout(fn, 0) se ejecute antes que los callbacks de I/O.
  • Ignorar el retraso del event loop hasta que aparecen problemas de latencia.

Próximos pasos

El event loop es el corazón de Node. Continúa con Streams para el I/O fragmentado y File System para el acceso asíncrono a archivos. Vuelve a consultar Node.js Basics si el runtime aún es nuevo para ti y, después, mide el retraso del event loop en un servicio que tengas en ejecución.

Leer un archivo

El trabajo asíncrono se ejecuta en el thread pool y libera el loop. Una llamada síncrona bloquea todas las demás solicitudes mientras el disco responde.

Preferir
import { readFile } from "node:fs/promises";

const data = await readFile("big.log", "utf8");
res.end(data);
Evitar
import { readFileSync } from "node:fs";

// blocks the entire server
const data = readFileSync("big.log", "utf8");
res.end(data);

Programar trabajo

nextTick se ejecuta antes que los callbacks de promises y antes de que el loop continúe. setImmediate se ejecuta en la fase de check después del I/O.

Preferir
// after current I/O, don't starve
setImmediate(() => processBatch());
Evitar
// recursive nextTick starves
// the loop and blocks I/O
process.nextTick(() => loop());

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender Event Loop?

Nuestro tutorial interactivo te guia a traves de Event Loop paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.