Node.js: Programação Assíncrona
Última atualização: 2026-08-26
1. Relato: Um serviço bloqueado por 200 ms
O serviço de API do Bob normalmente responde em apenas 5 ms, mas durante os horários de pico, os tempos de resposta dispararam para 2.000 ms. Os usuários não paravam de reclamar e, a princípio, ele achou que o banco de dados estava lento, mas adicionar índices não adiantou. Mais tarde, usando o console.time para solucionar o problema passo a passo, ele descobriu que a classificação de um grande array JSON síncrono estava levando 200 ms — essa operação de 200 ms estava bloqueando o loop de eventos, fazendo com que todas as solicitações na fila ficassem aguardando sua conclusão. Depois de entender como o loop de eventos funciona, Bob usou setImmediate para dividir a classificação em partes menores e executá-las uma a uma, reduzindo os tempos de resposta durante os horários de pico para 50 ms.
Você vai aprender:
- Responsabilidades e fluxo das 6 fases do ciclo de eventos
- Prioridade da fila de microtarefas (process.nextTick / Promise.then)
- Agendamento de filas de tarefas macro (setTimeout / setImmediate / E/S)
- setTimeout x setImmediate x ordem de execução do
process.nextTick - Por que
setImmediatetem prioridade sobresetTimeoutem um contexto de E/S - Os riscos de bloquear o ciclo de eventos e estratégias para dividi-lo
2. As 6 etapas do ciclo de eventos
O ciclo de eventos do Node.js é controlado pela libuv e passa por seis fases em sequência. Cada ciclo começa com os “temporizadores” e termina com os “callbacks de encerramento”, retornando então aos “temporizadores” para iniciar o próximo ciclo.
flowchart TD
A["timers<br/>setTimeout / setInterval"] --> B["pending callbacks<br/>System-Level Callbacks"]
B --> C["idle, prepare<br/>For internal use only"]
C --> D["poll<br/>I/O callbacks / new I/O polling"]
D --> E["check<br/>setImmediate"]
E --> F["close callbacks<br/>socket.on('close')"]
F --> A
| Fase | Tipo de callback | Descrição |
|---|---|---|
| temporizadores | setTimeout / setInterval | Executa a função de retorno do temporizador quando ele expira, sujeito a um atraso mínimo de 1 ms |
| callbacks pendentes | callbacks no nível do sistema | callbacks de E/S adiados para o próximo ciclo (por exemplo, erros de TCP) |
| inativo, preparar | Uso interno | Uso interno pela libuv; os desenvolvedores geralmente não interagem com ele |
| poll | callback de E/S | Verifica se há novos eventos de E/S e executa os callbacks relacionados à E/S; se não houver temporizadores, o processo ficará bloqueado nesta etapa |
| check | setImmediate | A função de retorno setImmediate é executada nesta etapa |
| callbacks de fechamento | eventos de fechamento | Como, por exemplo, socket.on('close') — callbacks de fechamento |
3. Microtarefas e macrotarefas
(1) (3.1) Fila de microtarefas
As microtarefas são executadas na íntegra após o término da fase atual e antes do início da próxima fase. A fila de microtarefas possui dois níveis de prioridade:
| Microtarefa | Prioridade | Descrição |
|---|---|---|
| process.nextTick | Máximo | Priorizar a limpeza da fila nextTick após o término de cada fase |
| Promise.then / catch / finally | Segundo maior | Executado após a fila nextTick ser esvaziada |
▶ Exemplo: nextTick tem precedência sobre Promise.then
process.nextTick(() => {
console.log('nextTick 1');
});
Promise.resolve().then(() => {
console.log('promise.then 1');
});
process.nextTick(() => {
console.log('nextTick 2');
});
Promise.resolve().then(() => {
console.log('promise.then 2');
});
nextTick 1
nextTick 2
promise.then 1
promise.then 2
(2) (3.2) Fila de macrotarefas
As macrotarefas são agendadas por cada fase do ciclo de eventos, sendo que cada fase executa suas macrotarefas correspondentes.
| Tarefa macro | Fase | Descrição |
|---|---|---|
| setTimeout / setInterval | temporizadores | Atraso mínimo de 1 ms; entra na fila de temporizadores ao expirar |
| setImmediate | check | Executado durante a fase “check” da próxima iteração |
| Callback de E/S | poll | Callback ao término de operações de E/S, como leitura/gravação de arquivos e operações de rede |
| callbacks de fechamento | callbacks de fechamento | Callbacks de evento de fechamento |
4. setTimeout x setImmediate x process.nextTick
▶ Exemplo: A ordem de execução dos contextos externos é indefinida
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
Em um contexto que não envolva E/S, a ordem de execução de setTimeout e setImmediate é indefinida — ela depende do agendamento de processos do sistema, e qualquer uma delas pode ser executada primeiro.
▶ Exemplo: Em um contexto de E/S, setImmediate é sempre executado primeiro
const fs = require('fs');
fs.readFile(__filename, () => {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
});
setImmediate
setTimeout
Os callbacks de E/S são executados durante a fase de sondagem; assim que são concluídos, o processo passa para a fase de verificação (setImmediate) e só então avança para a próxima rodada da fase dos temporizadores (setTimeout).
▶ Exemplo: process.nextTick sempre vem primeiro
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
process.nextTick(() => {
console.log('nextTick');
});
nextTick
setTimeout (or setImmediate, depends on scheduling)
setImmediate
| Recurso | setTimeout(fn, 0) | setImmediate | process.nextTick |
|---|---|---|---|
| Fila | Tarefas macro (temporizadores) | Tarefas macro (verificação) | Microtarefas (prioridade máxima) |
| Fase de execução | temporizadores | verificação | Imediatamente após o término da fase atual |
| Latência mínima | 1 ms | Nenhuma | Nenhuma |
| Ordem do contexto de E/S | Após setImmediate | Antes de setTimeout | Primeiro |
| Ordem do contexto externo | Indefinido | Indefinido | Mais antigo |
5. Comparação entre microtarefas e macrotarefas
| Dimensão | Microtarefa | Macrotarefa |
|---|---|---|
| Representante | process.nextTick, Promise.then | setTimeout, setImmediate, callbacks de E/S |
| Momento de execução | Após o término da fase atual e antes do início da próxima fase | Durante a fase correspondente do ciclo de eventos |
| Estratégia de execução | Limpar tudo de uma vez | Executar uma por fase e, em seguida, verificar as microtarefas |
| Aninhadas | As microtarefas geradas durante a execução também são eliminadas nesta rodada | Adiadas para uma etapa posterior para execução |
| Prioridade | Superior a todas as tarefas macro | Inferior às microtarefas |
▶ Exemplo: Microtarefas intercaladas entre macrotarefas
setTimeout(() => {
console.log('timeout 1');
Promise.resolve().then(() => {
console.log('promise between 1 and 2');
});
}, 0);
setTimeout(() => {
console.log('timeout 2');
}, 0);
timeout 1
promise between 1 and 2
timeout 2
Após a conclusão de cada macro-tarefa, a fila de micro-tarefas é esvaziada antes da execução da próxima macro-tarefa.
6. Os riscos de bloquear o ciclo de eventos e como evitá-los
▶ Exemplo: Bloqueando o ciclo de eventos
const http = require('http');
const server = http.createServer((req, res) => {
const start = Date.now();
while (Date.now() - start < 200) {}
res.end('done');
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});
Cada solicitação bloqueia o ciclo de eventos por 200 ms; durante esse tempo, nenhuma outra solicitação pode ser processada.
▶ Exemplo: Dividindo tarefas de longa duração com setImmediate
function chunkedSort(arr, compareFn, callback) {
const CHUNK = 1000;
let offset = 0;
function sortChunk() {
const end = Math.min(offset + CHUNK, arr.length);
for (let i = offset; i < end; i++) {
for (let j = i + 1; j < end; j++) {
if (compareFn(arr[i], arr[j]) > 0) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
}
offset = end;
if (offset < arr.length) {
setImmediate(sortChunk);
} else {
callback(arr);
}
}
setImmediate(sortChunk);
}
chunkedSort(
[5, 3, 8, 1, 9, 2, 7, 4, 6],
(a, b) => a - b,
(result) => {
console.log('Sorted:', result);
}
);
| Dimensão | Código de bloqueio | Código sem bloqueio |
|---|---|---|
| Método de cálculo | Executar em um único ciclo síncrono | Dividir em pequenos blocos e executar sequencialmente usando setImmediate |
| Ciclo de eventos | Bloqueado; outras solicitações estão em espera | O controle é cedido entre as fatias; outras chamadas de retorno podem ser executadas |
| Tempo de resposta | Atraso acumulado | Variação no atraso (aceitável para o usuário) |
| Utilização da CPU | 100% em um único núcleo (picos) | Utilização distribuída, reservando tempo para E/S |
| Casos de uso | Cálculos muito curtos | Cálculos longos, serviços de alta concorrência |
7. Exemplo completo: script para verificar a ordem de execução do ciclo de eventos
O script a seguir verifica a ordem completa de execução de setTimeout → process.nextTick → setImmediate → Promise.then → callback de E/S.
const fs = require('fs');
console.log('--- Script start ---');
setTimeout(() => {
console.log('1. setTimeout (macrotask - timers)');
}, 0);
setImmediate(() => {
console.log('2. setImmediate (macrotask - check)');
});
process.nextTick(() => {
console.log('3. process.nextTick (microtask - highest)');
});
Promise.resolve().then(() => {
console.log('4. Promise.then (microtask)');
});
fs.readFile(__filename, () => {
console.log('5. I/O callback (poll phase)');
setTimeout(() => {
console.log('6. inner setTimeout');
}, 0);
setImmediate(() => {
console.log('7. inner setImmediate');
});
process.nextTick(() => {
console.log('8. inner nextTick');
});
Promise.resolve().then(() => {
console.log('9. inner Promise.then');
});
});
console.log('--- Script end ---');
--- Script start ---
--- Script end ---
3. process.nextTick (microtask - highest)
4. Promise.then (microtask)
1. setTimeout (macrotask - timers)
2. setImmediate (macrotask - check)
5. I/O callback (poll phase)
8. inner nextTick
9. inner Promise.then
7. inner setImmediate
6. inner setTimeout
Análise da ordem de execução:
- O código síncrono é executado primeiro, exibindo “Início do script” e “Fim do script”
- Limpar as microtarefas após o término da fase atual: nextTick → Promise.then
- Entre na fase dos “temporizadores”: setTimeout
- Entrar na fase de verificação: setImmediate
- Executar a função de retorno de chamada durante a fase de sondagem, após a conclusão da E/S
- Limpe a microtarefa após a conclusão do callback de E/S: inner nextTick → inner Promise.then
- Quando a enquete terminar, insira o comando: inner setImmediate
- Próxima rodada de temporizadores: setTimeout interno
❓ Perguntas Frequentes
P: O
setTimeout(fn, 0)realmente é executado após 0 ms? R: Não. Tanto os navegadores quanto o Node.js têm um atraso mínimo (aproximadamente 1 ms), e a execução precisa aguardar até que o ciclo de eventos entre na fase “timers”; o tempo de espera real depende da fase atual.
P: Qual é executado primeiro,
process.nextTickouPromise.then? R:process.nextTické executado primeiro.nextTicktem a prioridade mais alta na fila de microtarefas, e a filanextTické sempre esvaziada antes da fila de Promises.
P: Por que
setImmediateé executado antes desetTimeoutem um callback de E/S? R: Os callbacks de E/S são executados durante a fasepoll. Assim que essa fase termina, o processo passa diretamente para a fasecheck(setImmediate) antes de prosseguir para a próxima rodada da fasetimers(setTimeout). Portanto, em um contexto de E/S,setImmediatesempre é executado antes desetTimeout.
P: O ciclo de eventos é de thread único ou multithread? R: A execução do JavaScript é de thread único, mas a libuv subjacente possui um pool de threads (4 threads por padrão) que lida com operações como consultas de DNS e E/S de arquivos. A E/S de rede é tratada pelo kernel do sistema operacional, que notifica o ciclo de eventos assim que a operação é concluída.
P: Como evitar bloquear o ciclo de eventos? R: Divida cálculos longos em partes menores e execute-os passo a passo usando
setImmediate; utilize threads de trabalho para transferir tarefas que exigem muito da CPU para threads separadas; use E/S assíncrona em vez de E/S síncrona; e utilize fluxos para processar grandes conjuntos de dados em partes.
P: É possível chamar
process.nextTickrecursivamente de forma indefinida? R: Isso não é recomendado. O Node.js possui um limite padrão definido porprocess.maxTickDepth; chamadas recursivas anextTickimpedirão que o ciclo de eventos entre na próxima fase, levando à escassez de E/S. Você deve usarsetImmediateem vez de chamadas recursivas denextTick.
📖 Resumo
- Matéria: Os conceitos fundamentais e o uso de um serviço bloqueado por 200 ms
- Conceitos-chave e uso das 6 fases do ciclo de eventos
- Conceitos básicos e uso de microtarefas e macrotarefas
- Conceitos-chave e uso de setTimeout, setImmediate e process.nextTick
- Conceitos-chave e uso de microtarefas versus macrotarefas
- Os riscos de bloquear o loop de eventos, conceitos-chave para evitá-lo e como fazê-lo
- Exemplo abrangente: Verificação da ordem de execução do ciclo de eventos — Conceitos básicos e uso de scripts
📝 Exercícios
- Conclua todos os exemplos de código desta lição e certifique-se de que cada um deles seja executado corretamente.
- Modifique o exemplo completo e adicione suas próprias extensões
- Analise a documentação oficial, identifique 1 ou 2 APIs que não foram abordadas nesta aula e escreva um código de teste para elas.
- Reflexão: Como você aplicaria o que aprendeu nesta aula a um projeto do mundo real?
- Tente combinar o que você aprendeu nesta aula com o conteúdo das aulas anteriores para criar um pequeno projeto.