12k
All articles

Como as Promises do JavaScript Funcionam com o Event Loop

As Promises do JavaScript usam a fila de microtasks para executar antes do setTimeout, tornando o código assíncrono previsível com base nas prioridades do event loop.

OpenReplay Team
OpenReplay Team
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

Por que Promise.resolve() executa antes de setTimeout(..., 0)?

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.

Muitas promises podem bloquear minha aplicação?

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.

Como async/await se relaciona com promises e o event loop?

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.

O que acontece se eu misturar diferentes padrões assíncronos no meu código?

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.

Open-source session replay

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.

Star on GitHub12k

We use cookies to improve your experience. By using our site, you accept cookies.