Você realmente sabe JavaScript? [Microtask vs Macrotask]
Calma, não precisa se ofender. Eu sei que você usa JavaScript todo dia, que manja de React, Next.js, e talvez até saiba a diferença entre let e const de olhos fechados. Mas me responda com honestidade: se eu te mostrar esse código abaixo, você consegue me dizer a ordem exata do que aparece no console?
// Microtask vs Macrotask
function logA() { console.log('A') }
function logB() { console.log('B') }
function logC() { console.log('C') }
function logD() { console.log('D') }
logA();
setTimeout(logB, 0);
Promise.resolve().then(logC);
logD();
Se você respondeu A, B, C, D... sinto muito, mas errou. E tudo bem — esse é um dos tópicos mais confusos (e mais cobrados em entrevistas!) do JavaScript. Vamos destrinchar isso juntos, passo a passo.
O Cenário
Temos quatro funções simples que fazem console.log de uma letra cada. Depois chamamos elas de formas diferentes: uma chamada direta (logA()), uma com setTimeout de 0ms (logB), uma com Promise.resolve().then() (logC) e outra chamada direta (logD()).
A pergunta de ouro é: em que ordem as letras aparecem no console?
Para entender isso, precisamos falar sobre o Event Loop, a Call Stack, a Microtask Queue e a Task Queue (Macrotask Queue).
Passo 1 — O motor JavaScript inicia a execução
Quando o script começa a rodar, o motor JavaScript cria um Global Execution Context e o empilha na Call Stack. A memória global está vazia e o console está limpo. Tudo pronto para começar a ler o código de cima para baixo.
Passo 2 — As funções são declaradas na memória
Antes de qualquer coisa ser executada, o JavaScript lê as declarações de função (linhas 2 a 5) e as armazena na memória.
Isso é o chamado hoisting: as funções ficam disponíveis antes mesmo de a gente chegar na linha que as chama.
Quando chegamos na linha 7, logA() é chamada. Um novo Execution Context é criado para ela e empilhado na Call Stack, em cima do contexto global.
Passo 3 — logA() executa e imprime "A"
logA() chama console.log('A') internamente. O resultado? A letra "A" aparece no console. Depois disso, tanto o console.log quanto o logA são removidos (desempilhados) da Call Stack. Até aqui, sem surpresas.
Passo 4 — O setTimeout entra em cena (Web APIs)
Agora vem a parte que confunde todo mundo. A linha 8 executa setTimeout(logB, 0). Repare: o delay é zero milissegundos. Intuitivamente, pensamos que logB vai rodar imediatamente, certo? Errado.
O setTimeout não é executado pelo motor JavaScript diretamente. Ele é delegado para as Web APIs do navegador. O timer começa a contar (neste caso, 0ms), mas o callback logB não volta para a Call Stack agora. Ele será enviado para a Task Queue somente depois que o timer expirar e a Call Stack estiver vazia.
Passo 5 — Promise.resolve().then(logC): a Microtask Queue
A linha 9 cria uma Promise já resolvida (Promise.resolve()) e registra o callback logC com .then().
Como a Promise já está fulfilled, o callback logC é enviado diretamente para a Microtask Queue. Repare: ele não vai para a mesma fila do setTimeout. São filas diferentes, com prioridades diferentes. E essa diferença é o coração de todo o mistério.
Observe na imagem: o setTimeout ainda está rodando nas Web APIs, e a Microtask Queue agora contém logC.
Passo 6 — logD() executa e imprime "D"
A linha 10 chama logD(), que é uma chamada síncrona e direta. Ela entra na Call Stack, executa, imprime "D", e sai.
O console agora mostra: A, D. Perceba que logB e logC ainda não executaram. Eles estão esperando suas respectivas filas serem processadas.
Passo 7 — A Call Stack esvazia: hora do Event Loop agir
Com logD() finalizada, a Call Stack fica vazia. É agora que o Event Loop entra em ação e faz a pergunta crucial:
"Tem alguma coisa na Microtask Queue?"
A resposta é sim — logC está lá esperando. E aqui está a regra de ouro: microtasks (Promises) sempre têm prioridade sobre macrotasks (setTimeout). O Event Loop drena toda a Microtask Queue antes de ir para a Task Queue.
Passo 8 — logC executa: a Microtask tem prioridade
logC é movida da Microtask Queue para a Call Stack e executa. A Microtask Queue agora aparece como "No microtasks".
O console agora mostra: A, D, C. Mesmo com o setTimeout tendo delay de 0ms, logC (da Promise) executou primeiro. Isso porque microtasks são processadas assim que a Call Stack esvazia, antes de qualquer macrotask.
Passo 9 — Agora sim, o setTimeout (Macrotask)
Com a Microtask Queue vazia, o Event Loop finalmente olha para a Task Queue (macrotask queue). O timer do setTimeout já expirou há muito tempo, e logB está esperando lá.
O callback do setTimeout foi movido das Web APIs para a Task Queue, e agora será promovido para a Call Stack.
logB entra na Call Stack e executa.
Resultado Final
O console mostra o resultado final: A, D, C, B.
A ordem faz total sentido agora:
- A — chamada síncrona direta, executa imediatamente
- D — outra chamada síncrona direta, executa imediatamente
- C — callback de Promise (microtask), tem prioridade sobre macrotasks
- B — callback de setTimeout (macrotask), executa por último
A Regra de Ouro
Quando a Call Stack esvazia, o Event Loop segue esta ordem:
Código síncrono → Microtasks (Promises, queueMicrotask) → Macrotasks (setTimeout, setInterval, I/O)
Não importa se o setTimeout tem delay de 0ms. Ele sempre vai para a Task Queue e sempre espera que todas as microtasks sejam processadas antes de ter sua vez.
Quer ver isso acontecendo ao vivo?
Todo o passo a passo que mostrei aqui foi feito usando o JavaScript Visualized (javascriptvisualized.com), uma ferramenta interativa e visual que mostra em tempo real como o motor JavaScript executa seu código. Você pode ver a Call Stack empilhando e desempilhando, as Web APIs processando timers, as filas de microtasks e macrotasks sendo drenadas — tudo com animações e explicações a cada passo.
Se você quer realmente entender JavaScript além da superfície, acesse javascriptvisualized.com e brinque com os exemplos. É a melhor forma de transformar conceitos abstratos em algo que você consegue ver e sentir acontecendo. Garanto que depois disso, nenhuma pergunta de entrevista sobre Event Loop vai te pegar desprevenido.