Voltar para todos os posts
·7 min de leitura

Event Loop no Node.js: O DJ Que Nunca Para de Tocar

Node.js é single-threaded — mas consegue lidar com milhares de operações ao mesmo tempo. Entenda como o Event Loop, a Call Stack e as filas de tarefas funcionam juntos, através da analogia de um DJ numa festa.

Node.jsJavaScriptEvent LoopAsyncIniciantes

Event Loop no Node.js: O DJ Que Nunca Para de Tocar

Node.js é single-threaded. Isso significa que ele só faz uma coisa por vez. E mesmo assim, consegue lidar com milhares de conexões simultâneas sem suar. Como?

A resposta é o Event Loop — e entendê-lo vai mudar a forma como você pensa sobre código assíncrono para sempre.

Vamos percorrer isso usando a analogia de um DJ numa festa. Fica comigo.

A Mesa do DJ (Call Stack)

Imagine um DJ numa festa. A mesa do DJ (a Call Stack) só consegue tocar uma música por vez. É isso. Não importa quantos pedidos chegam, o DJ processa um por um, em ordem.

Em termos de JavaScript, essa é a call stack — uma estrutura LIFO (Last In, First Out) onde funções são empilhadas quando chamadas e removidas quando retornam.

function receberConvidado(nome) {
  console.log(`Bem-vindo, ${nome}!`);
}

function abrirFesta() {
  receberConvidado("Alice");
  receberConvidado("Bob");
}

abrirFesta();
// Saída:
// Bem-vindo, Alice!
// Bem-vindo, Bob!

Cada chamada de função é empilhada, executada e removida antes da próxima rodar. Simples, sequencial, previsível.

Os Roadies (Internals do Node / libuv)

O DJ não pode sair da mesa para configurar a máquina de fumaça. Esse é o trabalho dos roadies.

No Node.js, os roadies são os internals do Node (libuv e a camada de async do sistema operacional — equivalente ao que os navegadores chamam de Web APIs): setTimeout, fetch, leitura de arquivos, requisições de rede. Quando você chama uma dessas, o Node passa a tarefa para o ambiente e continua imediatamente — o DJ continua tocando enquanto os roadies fazem o trabalho deles.

console.log("A festa começa");

setTimeout(() => {
  console.log("Máquina de fumaça pronta!");
}, 2000);

console.log("O DJ já está tocando");

// Saída:
// A festa começa
// O DJ já está tocando
// Máquina de fumaça pronta! (2 segundos depois)

O Node não parou e esperou 2 segundos. Delegou para o roadie e seguiu em frente. Isso é I/O não-bloqueante.

A Fila de Pedidos (Macrotask Queue)

Quando um roadie termina o trabalho, o resultado não vai direto para a mesa do DJ. Ele vai para a fila de pedidos — a Macrotask Queue.

O Event Loop (a disciplina do DJ) tem uma regra: só pegar da fila quando a mesa estiver completamente livre.

console.log("1 - DJ abre o set");

setTimeout(() => console.log("3 - Primeiro pedido da fila"), 0);
setTimeout(() => console.log("4 - Segundo pedido da fila"), 0);

console.log("2 - DJ termina a abertura");

// Saída:
// 1 - DJ abre o set
// 2 - DJ termina a abertura
// 3 - Primeiro pedido da fila
// 4 - Segundo pedido da fila

Mesmo com setTimeout(..., 0), os callbacks passam pela fila e só rodam depois que a call stack atual esvazia.

Exemplos de macrotasks: setTimeout, setInterval, callbacks de I/O, setImmediate (Node.js).

A Fila VIP (Microtask Queue)

Aqui é onde fica interessante.

Alguns convidados da festa são VIPs. Eles têm uma fila separada, e o DJ tem uma política rígida: após cada música (macrotask), esvaziar toda a fila VIP antes de tocar a próxima música da fila normal. Na verdade, a fila VIP também é processada logo que a call stack inicial esvazia — antes mesmo da primeira música da fila normal tocar.

A Microtask Queue funciona exatamente assim. Ela é processada completamente após cada macrotask, antes da próxima macrotask começar.

console.log("1 - Início");

setTimeout(() => console.log("5 - Macrotask (setTimeout)"), 0);

Promise.resolve()
  .then(() => console.log("3 - Microtask 1 (Promise)"))
  .then(() => console.log("4 - Microtask 2 (Promise encadeada)"));

console.log("2 - Fim do código síncrono");

// Saída:
// 1 - Início
// 2 - Fim do código síncrono
// 3 - Microtask 1 (Promise)
// 4 - Microtask 2 (Promise encadeada)
// 5 - Macrotask (setTimeout)

Os callbacks das Promises (microtasks) sempre rodam antes do callback do setTimeout (macrotask), mesmo que o setTimeout tenha sido registrado primeiro.

Exemplos de microtasks: Promise.then, Promise.catch, Promise.finally, queueMicrotask, continuações de async/await.

O Set Completo

Vamos juntar tudo com um exemplo completo:

console.log("1 - Síncrono: a festa começa");

setTimeout(() => console.log("6 - Macrotask: trabalho do roadie concluído"), 0);

Promise.resolve()
  .then(() => {
    console.log("4 - Microtask: convidado VIP chega");
  })
  .then(() => console.log("5 - Microtask: VIP recebe sua bebida"));

setTimeout(() => console.log("7 - Macrotask: segundo roadie concluído"), 0);

console.log("2 - Síncrono: DJ continua tocando");
console.log("3 - Síncrono: call stack esvazia");

// Saída:
// 1 - Síncrono: a festa começa
// 2 - Síncrono: DJ continua tocando
// 3 - Síncrono: call stack esvazia
// 4 - Microtask: convidado VIP chega
// 5 - Microtask: VIP recebe sua bebida
// 6 - Macrotask: trabalho do roadie concluído
// 7 - Macrotask: segundo roadie concluído

Ordem de execução:

  1. Todo o código síncrono (call stack esvazia completamente)
  2. Todas as microtasks (fila VIP — esvaziada completamente)
  3. Uma macrotask (fila normal)
  4. Todas as microtasks novamente (fila VIP — esvaziada novamente)
  5. Próxima macrotask... e assim por diante

3 Regras de Ouro

  1. Uma coisa por vez. A call stack é single-threaded — sem paralelismo — ela lida com muitas tarefas delegando, não fazendo tudo ao mesmo tempo.
  2. Microtasks sempre vêm antes das macrotasks. Após cada macrotask, esvaziar toda a fila de microtasks primeiro.
  3. Operações assíncronas não bloqueiam. Elas são delegadas ao ambiente (roadies), liberando a call stack para continuar trabalhando.

Entender essas três regras explica 90% do comportamento "estranho" que você vai encontrar com JavaScript assíncrono no Node.js.

F
Fernando Callata

© 2026. Excelência em primeiro lugar.