libro / blog / event-loop-nodejs

// fundamentos

El event loop de Node.js explicado con ejemplos

Gustavo Arias9 min de lectura

TL;DR Node.js ejecuta tu JavaScript en un solo hilo, pero delega la entrada/salida (red, disco) al sistema y recibe los resultados como callbacks que el event loop va ejecutando. Mientras no bloquees ese hilo con cálculos largos o funciones síncronas, un solo proceso atiende miles de conexiones.

La primera vez que alguien escucha que Node.js usa un solo hilo, la reacción suele ser de desconfianza: ¿cómo va a atender miles de usuarios a la vez? La respuesta es el event loop, y entenderlo es lo que separa a quien “usa” Node.js de quien sabe por qué su servidor se volvió lento de repente. En el libro Node.js desde cero es lo primero que explicamos; acá tenés la versión resumida, con ejemplos que podés ejecutar.

La idea: un hilo que nunca espera

Pensá en un mozo en un restaurante. Un mozo “bloqueante” toma un pedido, va a la cocina y se queda parado hasta que el plato está listo. Un mozo “no bloqueante” toma el pedido, lo deja en la cocina y sigue atendiendo otras mesas; cuando la cocina avisa, lleva el plato.

Node.js es el segundo mozo. Cuando tu código lee un archivo o consulta una base de datos, Node.js le pasa el trabajo al sistema operativo (o a un pequeño grupo de hilos internos de la librería libuv) y sigue ejecutando otras cosas. Cuando el resultado está listo, el callback correspondiente entra en una cola, y el event loop lo ejecuta en cuanto el hilo principal queda libre.

La clave: el hilo de JavaScript es uno solo, pero casi nunca está esperando. Pasa el tiempo ejecutando callbacks cortos uno detrás de otro.

Las fases del event loop

Cada vuelta del event loop recorre varias fases, cada una con su cola de callbacks:

FaseQué ejecuta
TimersCallbacks de setTimeout y setInterval cuyo tiempo ya venció
Pending callbacksAlgunos callbacks del sistema aplazados
PollCallbacks de entrada/salida (red, archivos); si no hay nada, espera aquí
CheckCallbacks de setImmediate
Close callbacksEventos de cierre, como socket.on('close')

Además, hay dos colas que se vacían entre cada callback, antes de pasar a lo siguiente: la de process.nextTick y la de las microtareas (promesas resueltas, await, queueMicrotask).

Ejemplo: ¿en qué orden se imprime?

console.log('1. código síncrono');

setTimeout(() => console.log('5. setTimeout'), 0);
setImmediate(() => console.log('5. setImmediate'));

Promise.resolve().then(() => console.log('4. promesa (microtarea)'));
process.nextTick(() => console.log('3. process.nextTick'));

console.log('2. código síncrono');

Al ejecutarlo con node orden.cjs, el resultado es:

1. código síncrono
2. código síncrono
3. process.nextTick
4. promesa (microtarea)
5. setTimeout       ← estos dos pueden aparecer
5. setImmediate     ← en cualquier orden
Ojo con ES Modules. Si guardás el mismo código como orden.mjs (o usás "type": "module"), la línea 4 aparece antes que la 3. En un ES Module, el propio código del módulo se evalúa dentro de una microtarea, así que las promesas pendientes se resuelven antes de llegar a la cola de nextTick. Es un detalle sutil, y un buen motivo para no depender de ese orden.

Moraleja práctica: no escribas código que dependa de ese orden. Si algo tiene que ocurrir después de otra cosa, encadenalo con await.

Cómo se bloquea el event loop

Como hay un solo hilo, cualquier trabajo largo y síncrono frena todo: mientras se ejecuta, ninguna otra petición avanza.

import { createServer } from 'node:http';

createServer((req, res) => {
  if (req.url === '/lento') {
    // 3 segundos de cálculo síncrono: durante ese tiempo, NADIE más es atendido
    const fin = Date.now() + 3000;
    while (Date.now() < fin) { /* calcular */ }
    return res.end('terminé\n');
  }
  res.end('rápido\n');
}).listen(3000);

Abrí /lento en una pestaña y, mientras carga, / en otra: la ruta “rápida” también tarda tres segundos. Los culpables habituales en el mundo real son:

Qué hacer con el trabajo pesado

  1. Usá siempre las versiones asíncronas de las APIs de Node.js (fs/promises, crypto con callback o promesas).
  2. Procesá por partes: con streams en lugar de cargar todo en memoria.
  3. Mové el cálculo a otro hilo con worker_threads, o a otro proceso o servicio (una cola de trabajos).
  4. Medí: perf_hooks.monitorEventLoopDelay() te dice cuánto se está retrasando el event loop; si sube, algo lo está bloqueando.
Para profundizar: la guía oficial The Node.js Event Loop.

Los fundamentos que sostienen todo lo demás

El libro Node.js desde cero empieza por la naturaleza asíncrona de Node.js y el event loop antes de llegar a Express, para que entiendas qué pasa por debajo.

Comprar en Amazon