Net Propaganda 🔍
Net Propaganda
Editorias
Institucional
Apps e Software

Event Loop Node.js: o que é e como funciona

O event loop é o coração do modelo de concorrência do Node.js. Quando alguém pergunta "o que é event loop e como funciona em Node.js", a resposta direta é: trata-se de um mecanismo que permite ao Node.js realizar operações de entrada e saída (I/O) de forma não bloqueante, mesmo que o JavaScript seja executado em uma única thread. Em vez de esperar uma operação terminar para seguir adiante, o Node.js delega tarefas demoradas ao sistema operacional ou a threads internas e, quando a tarefa é concluída, o event loop a processa em um momento oportuno. Entender esse fluxo é essencial para escrever código eficiente e evitar gargalos em aplicações reais, principalmente aquelas que lidam com muitas requisições simultâneas.

O que exatamente é o event loop?

O event loop é um laço infinito que o Node.js executa para gerenciar eventos e callbacks. Ele faz parte do próprio runtime, não do JavaScript em si. Pense nele como um maestro que decide qual parte do código deve ser executada em cada momento. Quando o Node.js inicia, ele processa o script principal, registra callbacks para operações assíncronas (como ler um arquivo ou fazer uma requisição HTTP) e, em seguida, entra no loop. O loop verifica constantemente se há novas tarefas na fila de eventos, executa os callbacks correspondentes e repete o ciclo.

Uma característica central é que o event loop é single-threaded, mas as operações de I/O podem ser delegadas a threads do sistema operacional ou ao thread pool interno do Node.js (como no caso do módulo fs). Isso significa que o JavaScript em si nunca executa duas coisas ao mesmo tempo, mas o Node.js consegue lidar com várias operações simultâneas sem travar o processo.

Como o event loop executa código assíncrono na prática?

Na prática, o event loop funciona em fases, cada uma com uma fila própria. A ordem aproximada é: timers (como setTimeout e setInterval), callbacks de I/O pendentes, fase de espera/idle, poll (onde a maioria das operações de I/O é processada), check (para setImmediate) e close (para eventos de fechamento, como socket.on('close')).

Um exemplo concreto: ao executar setTimeout(callback, 0), o callback não é executado imediatamente. Ele é agendado para a fase de timers e só roda quando o event loop chega nessa fase. Da mesma forma, uma leitura de arquivo com fs.readFile é enviada para o thread pool; quando a leitura termina, o callback correspondente entra na fila de I/O e é executado na fase de poll. Isso explica por que a ordem de execução de código assíncrono nem sempre é intuitiva: depende do tipo de operação e da fase atual do loop.

Um ponto de atenção: se o código síncrono demorar muito, o event loop fica bloqueado e nenhum callback é executado até que a thread termine o trabalho atual. Por isso, operações síncronas pesadas (como um for que processa milhões de itens) devem ser evitadas em servidores Node.js, pois travam todo o processamento de novas requisições.

O que são as filas de microtasks e macrotasks?

Dentro do event loop, existe uma distinção importante entre microtasks e macrotasks. Macrotasks incluem setTimeout, setInterval, setImmediate e operações de I/O. Microtasks incluem promessas (Promise.then, catch, finally) e queueMicrotask. As microtasks têm prioridade: após cada macrotask, o event loop esvazia toda a fila de microtasks antes de passar para a próxima macrotask.

Isso gera um comportamento que confunde muitos desenvolvedores: se você tem um setTimeout e uma promessa resolvida, a promessa será executada primeiro, mesmo que o timeout tenha sido agendado antes. Um exemplo clássico:

setTimeout(() => console.log('timeout'), 0); Promise.resolve().then(() => console.log('promise'));

A saída será promise e depois timeout, porque a microtask da promessa é processada antes da macrotask do timer. Esse entendimento é crucial para depurar código assíncrono e prever a ordem de execução.

Por que o event loop é importante para o desempenho?

O event loop é o que permite ao Node.js lidar com milhares de conexões simultâneas usando poucos recursos. Em um servidor tradicional baseado em threads, cada conexão consumiria uma thread dedicada, o que aumenta o uso de memória e o custo de troca de contexto. No Node.js, uma única thread gerencia todas as conexões, delegando operações de I/O e retomando o processamento quando os dados estão prontos.

Na prática, isso significa que aplicações Node.js são particularmente boas para cenários de I/O intensivo, como APIs REST, proxies e serviços de streaming. Por outro lado, operações que exigem muito processamento da CPU, como cálculos complexos ou manipulação de imagens, podem travar o event loop e degradar a performance. Nesses casos, a recomendação é usar worker threads ou serviços externos especializados.

Quais são os erros comuns ao usar o event loop?

Um erro frequente é assumir que todas as funções assíncronas são executadas em paralelo. Na verdade, elas são executadas de forma concorrente, mas não simultânea. O event loop intercala a execução, mas nunca executa dois callbacks ao mesmo tempo. Outro erro é usar fs.readFileSync ou outras operações síncronas dentro de um servidor, pois isso bloqueia o loop por completo, impedindo que outras requisições sejam atendidas.

Também é comum subestimar o custo de promessas mal utilizadas. Se uma promessa encadeia muitos .then, cada um deles é uma microtask, e uma quantidade excessiva de microtasks pode atrasar a execução de macrotasks, criando um efeito de starvation. Um exemplo: um loop que cria milhões de promessas resolvidas pode impedir que timers sejam executados em tempo hábil.

Como observar o event loop em ação?

Para ver o event loop na prática, o Node.js oferece ferramentas como process._getActiveHandles() e process._getActiveRequests(), que listam os handles e requisições ativos. Também é possível usar o módulo perf_hooks para medir a duração de cada fase do loop, embora isso exija um pouco mais de configuração. Em aplicações maiores, o diagnóstico de bloqueios pode ser feito com o profiler do Chrome DevTools, conectado ao processo Node.js.

Uma observação importante: o event loop não é uma abstração que você controla diretamente. Você não "chama" o event loop; ele roda automaticamente enquanto houver trabalho a fazer. Quando não há mais tarefas pendentes, o Node.js encerra o processo, o que explica por que um script simples com apenas um setTimeout termina após o timer ser executado.

Resumo: o que guardar sobre o event loop

O event loop é um mecanismo de agendamento que permite ao Node.js ser assíncrono e não bloqueante, apesar do JavaScript single-threaded. Ele funciona com fases e filas, priorizando microtasks sobre macrotasks. Para escrever código eficiente, evite operações síncronas pesadas, entenda a ordem de execução de promessas e timers, e lembre-se de que o loop é único e compartilhado por toda a aplicação.

Perguntas frequentes sobre o event loop no Node.js

O event loop é uma thread?

Não. O event loop é um mecanismo de controle de fluxo que roda na thread principal do Node.js. Ele não é uma thread separada, mas sim um laço que gerencia a execução de callbacks e eventos. Operações de I/O podem usar threads auxiliares, mas o loop em si é single-threaded.

Por que o Node.js é single-threaded se o event loop é assíncrono?

O JavaScript em si é single-threaded, ou seja, executa um comando por vez. O event loop não muda isso; ele apenas organiza a ordem em que as tarefas são executadas. Operações demoradas são delegadas ao sistema, e o loop retoma o processamento quando elas terminam, sem nunca executar dois trechos de JavaScript simultaneamente.

Qual a diferença entre setImmediate e setTimeout?

Ambos são macrotasks, mas têm fases diferentes no event loop. setTimeout é executado na fase de timers, enquanto setImmediate é executado na fase de check, que ocorre depois da fase de poll. Na prática, se ambos forem chamados fora de um módulo I/O, a ordem pode variar; dentro de um callback de I/O, setImmediate tende a executar primeiro.

O que acontece se o event loop ficar bloqueado?

Se a thread principal ficar ocupada com código síncrono pesado, o event loop não consegue processar novas tarefas. Isso significa que callbacks de timers, I/O e promessas ficam atrasados, e a aplicação pode parecer travada. Em servidores, isso se manifesta como lentidão ou perda de requisições.

Como evitar bloquear o event loop?

Evite operações síncronas no código principal, prefira versões assíncronas (fs.readFile em vez de fs.readFileSync). Para tarefas pesadas de CPU, use worker threads ou divida o trabalho em partes menores. Monitore a duração das fases do loop com ferramentas de profiling para identificar gargalos.

O event loop é exclusivo do Node.js?

Não. O conceito de event loop existe em outros runtimes e linguagens, como o navegador (com o loop de eventos do JavaScript) e o Python (com asyncio). No Node.js, ele é implementado pela biblioteca libuv, que também gerencia o thread pool e as operações de I/O do sistema.

Ptolomeu Rangel Sicupira
Pesquisador de tendências e cultura digital
Lê a internet como antropólogo; conecta meme, comportamento e estratégia de marca.
Ver todos os artigos →