Executando verificação de segurança...
13

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

image

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.

image

Isso é o chamado hoisting: as funções ficam disponíveis antes mesmo de a gente chegar na linha que as chama.

image

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"

image

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.

image

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().

image

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.

image

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?"

image

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

image

logC é movida da Microtask Queue para a Call Stack e executa. A Microtask Queue agora aparece como "No microtasks".

image

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á.

image

O callback do setTimeout foi movido das Web APIs para a Task Queue, e agora será promovido para a Call Stack.

image

logB entra na Call Stack e executa.


Resultado Final

image

O console mostra o resultado final: A, D, C, B.

A ordem faz total sentido agora:

  1. A — chamada síncrona direta, executa imediatamente
  2. D — outra chamada síncrona direta, executa imediatamente
  3. C — callback de Promise (microtask), tem prioridade sobre macrotasks
  4. 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.

Carregando publicação patrocinada...
5
1

pô, muito legal esse artigo! Sempre me confundi com essa questão de microtasks e macrotasks. Quando comecei a estudar JavaScript, levei um tempo pra entender como essas promessas e timeouts funcionam. Na real, a ordem correta é A, D, C, B, e essa cagada no console pode ser um baita susto durante uma entrevista. Já passei por isso, é uma situação complicada!

1
1

galera que sabe mais de node que chrome:

geralmente eu opto por setInterval em detrimento a promisse e assinc
geralmente eu opto por alert em detrimento a console.log

isso pq eu fazia requisicao assincrona de otas manera em 2010
era 'praticamente' oto chrome

e geralmente eu opto por php server do que js server

meu 'equívoco' foi optar por programar para o browser em detrimento a optar por programar para o server

0