O event loop a fundo
O Node é single-thread para o seu código JavaScript: uma única pilha executa uma coisa de cada vez. A concorrência surge do event loop, que decide o que executa em seguida quando a pilha fica vazia. Nem todas as tarefas pendentes são iguais:
- Microtarefas (microtasks): as continuações de promessas (
.then, o código após umawait) equeueMicrotask. São esvaziadas inteiras assim que a pilha atual termina, antes de passar para a próxima macrotarefa. - Macrotarefas (macrotasks):
setTimeout,setInterval, I/O. São processadas uma por iteração do loop, depois de drenar todas as microtarefas.
Por isso uma promessa já resolvida "ganha" de um setTimeout(…, 0):
console.log("1: síncrono");
setTimeout(() => console.log("4: timeout (macrotarefa)"), 0);
Promise.resolve().then(() => console.log("3: promessa (microtarefa)"));
console.log("2: síncrono");
// Ordem real: 1, 2, 3, 4
Primeiro todo o código síncrono (1, 2). Ao esvaziar a pilha, o loop drena as microtarefas (3). Só então pega a próxima macrotarefa (4).
Streams e backpressure
Carregar um arquivo de 2 GB na memória com readFile o estoura. Um stream
processa dados em pedaços (chunks), com um uso de memória constante:
import { createReadStream, createWriteStream } from "node:fs";
createReadStream("entrada.bin").pipe(createWriteStream("saida.bin"));
E se o destino for mais lento que a origem (um disco lento, uma rede congestionada)?
Aí entra o backpressure ("contrapressão"): o stream de escrita avisa que
seu buffer está cheio (write() retorna false), e o de leitura se pausa até
que se esvazie (evento drain). O pipe() gerencia isso para você automaticamente, evitando
que a memória cresça sem controle.
Buffer: dados binários
As cadeias de texto não bastam para imagens, áudio ou protocolos binários. Um
Buffer é uma zona de memória de bytes crus (inteiros de 0 a 255):
const buf = Buffer.from("AB");
console.log(buf.length); // 2 bytes
console.log(buf[0]); // 65 (código do caractere 'A')
console.log(buf.toString("hex")); // "4142"
Os chunks que percorrem um stream são, de fato, Buffers.
Exemplos
Microtarefa (promessa) antes da macrotarefa (timeout)
console.log("A");
setTimeout(() => console.log("D"), 0);
Promise.resolve().then(() => console.log("C"));
console.log("B");
// Imprime: A, B, C, D