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.
Las fases del event loop
Cada vuelta del event loop recorre varias fases, cada una con su cola de callbacks:
| Fase | Qué ejecuta |
|---|---|
| Timers | Callbacks de setTimeout y setInterval cuyo tiempo ya venció |
| Pending callbacks | Algunos callbacks del sistema aplazados |
| Poll | Callbacks de entrada/salida (red, archivos); si no hay nada, espera aquí |
| Check | Callbacks de setImmediate |
| Close callbacks | Eventos 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
- Primero corre todo el código síncrono del módulo.
- Después se vacían
nextTicky las microtareas. - Recién entonces arranca el event loop. Entre
setTimeout(…, 0)ysetImmediateel orden no está garantizado cuando se llaman desde el código principal: depende de cuánto tarda el proceso en arrancar. Dentro de un callback de entrada/salida, en cambio,setImmediatesiempre va primero.
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:
- Funciones síncronas de archivos en un servidor:
readFileSync,writeFileSync. JSON.parseoJSON.stringifyde objetos enormes.- Bucles sobre cientos de miles de elementos, cifrado o compresión síncronos.
- Expresiones regulares mal escritas que entran en backtracking catastrófico.
Qué hacer con el trabajo pesado
- Usá siempre las versiones asíncronas de las APIs de Node.js
(
fs/promises,cryptocon callback o promesas). - Procesá por partes: con streams en lugar de cargar todo en memoria.
- Mové el cálculo a otro hilo con
worker_threads, o a otro proceso o servicio (una cola de trabajos). - Medí:
perf_hooks.monitorEventLoopDelay()te dice cuánto se está retrasando el event loop; si sube, algo lo está bloqueando.
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