Back

Como as Promises do JavaScript Funcionam com o Event Loop

Como as Promises do JavaScript Funcionam com o Event Loop

O comportamento assíncrono do JavaScript frequentemente confunde desenvolvedores. Você escreve setTimeout(() => console.log('timeout'), 0) esperando execução imediata, mas uma Promise é resolvida primeiro. Compreender como as Promises do JavaScript interagem com o Event Loop revela por que isso acontece e ajuda você a escrever código assíncrono mais previsível.

Pontos-Chave

  • O event loop processa todas as microtasks antes de passar para a próxima macrotask
  • Promises e async/await utilizam a fila de microtasks, ganhando prioridade sobre setTimeout
  • Compreender o sistema de duas filas ajuda a prever a ordem de execução do JavaScript
  • Microtasks recursivas podem esgotar o event loop e bloquear macrotasks

A Base do Event Loop do JavaScript

O JavaScript executa em uma única thread, processando uma operação por vez. O event loop possibilita operações assíncronas coordenando entre a pilha de chamadas (call stack) e duas filas distintas: a fila de microtasks e a fila de macrotasks.

A call stack executa código síncrono imediatamente. Quando a pilha esvazia, o event loop verifica tarefas pendentes em uma ordem específica:

  1. Todas as microtasks são executadas primeiro
  2. Uma macrotask é executada
  3. O ciclo se repete

Este sistema de prioridade explica por que promises se comportam de forma diferente do setTimeout.

Macrotask vs Microtask no JavaScript: A Diferença Crítica

Compreender as distinções entre macrotask vs microtask é essencial para prever a ordem de execução do código.

Macrotasks incluem:

  • setTimeout
  • setInterval
  • Operações de I/O
  • Renderização de UI

Microtasks incluem:

  • Callbacks de Promise (.then, .catch, .finally)
  • queueMicrotask()
  • Callbacks de MutationObserver

O event loop processa todas as microtasks antes de passar para a próxima macrotask. Isso cria um sistema de prioridade onde promises sempre executam antes de timers.

Promises e a Fila de Microtasks em Ação

Vamos examinar como promises interagem com o event loop através de código:

console.log('1');

setTimeout(() => console.log('2'), 0);

Promise.resolve()
  .then(() => console.log('3'))
  .then(() => console.log('4'));

console.log('5');

Saída: 1, 5, 3, 4, 2

Aqui está o fluxo de execução:

  1. console.log('1') síncrono executa imediatamente
  2. setTimeout agenda callback para a fila de macrotasks
  3. Callbacks de Promise entram na fila como microtasks
  4. console.log('5') síncrono executa
  5. Event loop processa todas as microtasks (3, 4)
  6. Event loop processa uma macrotask (2)

A fila de microtasks esvazia completamente antes que qualquer macrotask seja executada, mesmo com timeouts de atraso zero.

Integração de Async/Await com o Event Loop

O comportamento de async/await segue as mesmas regras de microtasks. A palavra-chave await pausa a execução da função e agenda a continuação como uma microtask:

async function example() {
  console.log('1');
  await Promise.resolve();
  console.log('2');  // Isso se torna uma microtask
}

example();
console.log('3');

// Saída: 1, 3, 2

Após o await, o corpo restante da função entra na fila de microtasks. Isso explica por que console.log('3') executa antes de console.log('2') apesar de aparecer depois no código.

Armadilhas Comuns e Padrões Práticos

Esgotamento da Fila de Microtasks

Criar microtasks recursivamente pode bloquear o event loop:

function dangerousLoop() {
  Promise.resolve().then(dangerousLoop);
}
// Não faça isso - bloqueia todas as macrotasks

Misturando Timers com Promises

Ao combinar diferentes padrões assíncronos, lembre-se da prioridade de execução:

setTimeout(() => console.log('timeout'), 0);

fetch('/api/data')
  .then(response => response.json())
  .then(data => console.log('data'));

Promise.resolve().then(() => console.log('immediate'));

// Ordem: immediate → data (quando pronto) → timeout

Depurando a Ordem de Execução

Use este padrão para rastrear o fluxo de execução:

console.log('Sync start');

queueMicrotask(() => console.log('Microtask 1'));

setTimeout(() => console.log('Macrotask 1'), 0);

Promise.resolve()
  .then(() => console.log('Microtask 2'))
  .then(() => console.log('Microtask 3'));

setTimeout(() => console.log('Macrotask 2'), 0);

console.log('Sync end');

// Saída: Sync start, Sync end, Microtask 1, Microtask 2, Microtask 3, Macrotask 1, Macrotask 2

Conclusão

O sistema de duas filas do event loop determina a ordem de execução assíncrona do JavaScript. Promises e async/await utilizam a fila de microtasks, ganhando prioridade sobre setTimeout e outras macrotasks. Este conhecimento transforma comportamento assíncrono misterioso em padrões previsíveis, permitindo que você escreva código JavaScript assíncrono mais confiável.

Lembre-se: código síncrono executa primeiro, depois todas as microtasks são limpas, então uma macrotask é executada. Este ciclo se repete, fazendo com que a única thread do JavaScript lide com operações assíncronas complexas de forma eficiente.

Perguntas Frequentes

Callbacks de Promise são microtasks enquanto setTimeout cria macrotasks. O event loop sempre processa todas as microtasks antes de passar para a próxima macrotask, independentemente da duração do timeout.

Sim, criar promises ou microtasks continuamente sem permitir que macrotasks sejam executadas pode esgotar o event loop. Isso impede que atualizações de UI e outras macrotasks sejam executadas.

Async/await é açúcar sintático para promises. A palavra-chave await pausa a execução e agenda a continuação como uma microtask, seguindo as mesmas regras de prioridade dos callbacks de promise.

Diferentes padrões seguem suas regras de fila. Promises e async/await usam microtasks, enquanto setTimeout e setInterval usam macrotasks. Microtasks sempre executam primeiro quando a pilha de chamadas está vazia.

Complete picture for complete understanding

Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.

Check our GitHub repo and join the thousands of developers in our community.

OpenReplay