El event loop a fondo
Node es monohilo para tu código JavaScript: una sola pila ejecuta una cosa a la vez. La concurrencia surge del event loop, que decide qué se ejecuta después cuando la pila queda vacía. No todas las tareas pendientes son iguales:
- Microtareas (microtasks): las continuaciones de promesas (
.then, el código tras unawait) yqueueMicrotask. Se vacían enteras en cuanto la pila actual termina, antes de pasar a la siguiente macrotarea. - Macrotareas (macrotasks):
setTimeout,setInterval, I/O. Se procesan una por iteración del loop, después de drenar todas las microtareas.
Por eso una promesa ya resuelta "gana" a un setTimeout(…, 0):
console.log("1: síncrono");
setTimeout(() => console.log("4: timeout (macrotarea)"), 0);
Promise.resolve().then(() => console.log("3: promesa (microtarea)"));
console.log("2: síncrono");
// Orden real: 1, 2, 3, 4
Primero todo el código síncrono (1, 2). Al vaciarse la pila, el loop drena las microtareas (3). Solo entonces toma la siguiente macrotarea (4).
Streams y backpressure
Cargar un fichero de 2 GB en memoria con readFile lo revienta. Un stream
procesa datos por trozos (chunks), con un uso de memoria constante:
import { createReadStream, createWriteStream } from "node:fs";
createReadStream("entrada.bin").pipe(createWriteStream("salida.bin"));
¿Y si el destino es más lento que el origen (un disco lento, una red congestionada)?
Ahí entra el backpressure ("contrapresión"): el stream de escritura avisa de que
su buffer está lleno (write() devuelve false), y el de lectura se pausa hasta
que se vacía (evento drain). pipe() gestiona esto por ti automáticamente, evitando
que la memoria crezca sin control.
Buffer: datos binarios
Las cadenas de texto no bastan para imágenes, audio o protocolos binarios. Un
Buffer es una zona de memoria de bytes crudos (enteros de 0 a 255):
const buf = Buffer.from("AB");
console.log(buf.length); // 2 bytes
console.log(buf[0]); // 65 (código del carácter 'A')
console.log(buf.toString("hex")); // "4142"
Los chunks que recorren un stream son, de hecho, Buffers.
Ejemplos
Microtarea (promesa) antes que macrotarea (timeout)
console.log("A");
setTimeout(() => console.log("D"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("B");
// Imprime: A, B, C, D