W3docs

Event Loop do JavaScript, Microtarefas e Macrotarefas

Aprenda como o event loop do JavaScript funciona e como microtarefas (Promises) e macrotarefas (timers, eventos) são agendadas, com exemplos executáveis.

O JavaScript executa seu código em uma única thread: uma coisa acontece por vez, de cima para baixo. Mesmo assim, ele consegue buscar dados, executar timers e responder a cliques sem travar. O mecanismo que torna isso possível é o event loop, e o trabalho que ele agenda é dividido em dois tipos de tarefas: microtarefas e macrotarefas. Esta página explica o que é cada uma, a ordem em que são executadas e as armadilhas que costumam pegar as pessoas de surpresa — todos os exemplos aqui são executáveis para que você possa verificar a saída por conta própria.

Como Funciona o Event Loop

O event loop é o agendador que decide qual trecho de código será executado a seguir. Para entendê-lo, você precisa de apenas três partes:

  1. Call stack — onde seu código realmente é executado. As funções são empilhadas quando chamadas e removidas quando retornam. O JavaScript executa tudo que está na pilha até o fim antes de fazer qualquer outra coisa; esta é a regra de run-to-completion.
  2. Heap — a memória onde seus objects vivem. Não está diretamente envolvida no agendamento, mas é a terceira peça que as pessoas esperam ver mencionada.
  3. Filas de tarefas — trabalho pendente aguardando que a pilha esteja vazia. Existem duas: a fila de macrotarefas (timers, eventos de UI, I/O) e a fila de microtarefas (callbacks de Promise e queueMicrotask).

Um ciclo do event loop funciona assim:

  1. Executa a tarefa atual na pilha até que a pilha esteja completamente vazia.
  2. Esvazia a fila de microtarefas inteira — incluindo quaisquer microtarefas adicionadas durante o esvaziamento.
  3. (Em um navegador) renderiza quaisquer atualizações visuais pendentes.
  4. Retira uma macrotarefa da fila de macrotarefas e a executa, depois volta ao passo 2.

A assimetria principal: após cada macrotarefa, o motor esvazia todas as microtarefas, mas só retira uma macrotarefa por ciclo do loop. Essa única regra explica quase toda surpresa de ordenação que você vai encontrar.

Aqui está a demonstração mais simples possível, usando setTimeout para agendar uma macrotarefa:

javascript— editable

Neste exemplo:

  1. console.log('Start'); é executado primeiro, imprimindo "Start" no console.
  2. setTimeout agenda um callback para ser executado após pelo menos 1000 milissegundos. Ele retorna instantaneamente e não bloqueia as linhas abaixo dele.
  3. console.log('End'); é executado imediatamente, imprimindo "End".
  4. Somente depois que o script síncrono termina (e o atraso decorreu) o event loop retira o callback do setTimeout da fila de macrotarefas e o executa, imprimindo "Timeout Callback".

A saída é Start, End e depois Timeout Callback — o callback do timer aguarda mesmo tendo sido escrito no meio. O callback do setTimeout é uma macrotarefa: ele só é executado depois que o script em execução atual e todas as microtarefas pendentes forem concluídas. É isso que mantém a página responsiva — o código síncrono nunca precisa esperar por um timer ou requisição de rede.

Microtarefas vs. Macrotarefas

O que são Macrotarefas?

Uma macrotarefa (também chamada simplesmente de "task") é uma unidade de trabalho única e autossuficiente que o motor processa uma vez por ciclo do loop. As fontes mais comuns são:

  • setTimeout / setInterval: timers que executam um callback após um atraso ou repetidamente.
  • Eventos DOM: um handler de click, scroll ou input.
  • I/O: respostas de rede, leituras de arquivo e similares.

O motor executa exatamente uma macrotarefa, depois esvazia todas as microtarefas, depois (no navegador) pode renderizar, antes de pegar a próxima macrotarefa. Portanto, as macrotarefas nunca são executadas de forma consecutiva sem que a fila de microtarefas seja esvaziada entre elas.

O que são Microtarefas?

Uma microtarefa é uma tarefa curta que o motor deseja concluir assim que a unidade de código atual terminar — antes de ceder para a próxima macrotarefa ou para a renderização. Elas vêm de:

  • Callbacks de Promise: as funções passadas para .then(), .catch() e .finally(), além do corpo de uma função async após um await.
  • queueMicrotask(fn): uma função nativa que agenda uma função diretamente na fila de microtarefas.

A diferença crucial: após a tarefa atual, o motor esvazia a fila de microtarefas inteira antes de fazer qualquer outra coisa. Se uma microtarefa agendar outra microtarefa, a nova também é executada no mesmo esvaziamento — antes que a próxima macrotarefa tenha vez.

Exemplos de Código do Mundo Real

Exemplo 1: Um timer é uma macrotarefa

Imagine que você quer exibir uma mensagem após 2 segundos. A linha de agendamento é executada agora; o callback fica na fila de macrotarefas até que o atraso passe e a pilha esteja livre.

javascript— editable

Explicação: setTimeout retorna instantaneamente, então ambas as linhas console.log fora dele são executadas primeiro. O callback é uma macrotarefa que só é executada depois que o script síncrono terminar e o timer disparar. Em um navegador, você normalmente atualizaria o DOM dentro do callback, por exemplo document.getElementById('message').textContent = 'Hello there!';.

Exemplo 2: Um callback de Promise é uma microtarefa

O callback .then() de uma Promise resolvida não é executado de forma inline — ele é enfileirado como uma microtarefa e é executado assim que o código síncrono atual termina.

javascript— editable

Explicação: A saída é Before the promise, After the promise e depois Promise resolved (microtask). Mesmo que a Promise já esteja resolvida, o seu callback .then() aguarda na fila de microtarefas até que o código síncrono termine — e então é executado antes de qualquer timer.

Mais sobre a Prioridade de Micro e Macrotarefas

As microtarefas sempre têm prioridade maior do que as macrotarefas. Após o script atual terminar, o motor esvazia todas as microtarefas pendentes antes de processar uma única macrotarefa — mesmo um setTimeout(..., 0) que foi agendado primeiro. Observe no exemplo abaixo que a Promise 2 encadeada, criada dentro de uma microtarefa, ainda é executada antes de qualquer timer, porque a fila de microtarefas é esvaziada completamente antes de o loop continuar.

javascript— editable

Saída esperada:

Start
End
Promise 1
Promise 2
Timeout 1
Timeout 2

Isso mostra que as microtarefas são executadas imediatamente após o código síncrono, mesmo antes dos timers agendados para o mesmo momento. A priorização significa que as atualizações baseadas em Promise são resolvidas o mais rápido possível.

Uma Armadilha: Starvation de Microtarefas

Como o motor esvazia a fila de microtarefas inteira antes da próxima macrotarefa ou de uma renderização, uma microtarefa que continua agendando mais microtarefas pode bloquear tudo — os timers nunca disparam e a página não consegue repintar. Isso é chamado de starvation de microtarefas:

javascript— editable

As cinco microtarefas são todas executadas antes do callback do setTimeout, mesmo que o timer tenha sido agendado primeiro. Em um aplicativo real, uma versão ilimitada desse loop congelaria a interface. A solução é dividir o trabalho de longa duração em macrotarefas (por exemplo, setTimeout(..., 0)), o que permite que o event loop renderize e processe eventos entre os trechos.

Quando Usar Cada Um

  • Use microtarefas (Promises, queueMicrotask) quando quiser que o código seja executado assim que a operação atual terminar, mas ainda de forma assíncrona — como reagir a dados logo após um fetch ser resolvido.
  • Use macrotarefas (setTimeout, divisão de trabalho entre timers) quando quiser deliberadamente ceder ao navegador para que ele possa renderizar ou processar entradas antes de continuar — por exemplo, dividindo uma computação pesada em partes para que a página permaneça responsiva.

Conclusão

O event loop executa seu código síncrono até o fim, depois esvazia todas as microtarefas, depois processa uma macrotarefa, e repete. Microtarefas (callbacks de Promise, queueMicrotask) sempre são executadas antes da próxima macrotarefa (timers, eventos, I/O). Internalizar essa única regra permite prever a ordem exata de qualquer código assíncrono.

Para se aprofundar, continue com Promises, encadeamento de Promises, async/await e o capítulo dedicado a microtarefas. Para as APIs de timer usadas aqui, consulte agendamento com setTimeout e setInterval.

Prática

Prática
Em JavaScript, o que acontece quando uma promise é resolvida e há um handler `.then()` anexado?
Em JavaScript, o que acontece quando uma promise é resolvida e há um handler `.then()` anexado?
Was this page helpful?