Event loop, microtasks e macrotasks
await parece pausar tudo, mas por baixo é a call stack, a fila de callbacks e a fila de microtasks decidindo quem roda primeiro.
Você já sabe usar Promise e async/await. A pergunta que fica em aberto: JavaScript roda numa única thread — então como uma Promise "espera" alguma coisa sem travar o programa inteiro? A resposta é o event loop.
Call stack — onde o código síncrono roda
A call stack é onde funções são executadas, uma de cada vez, cada uma empilhada sobre a anterior até retornar. Enquanto tem algo na call stack, nada mais roda — é por isso que uma função síncrona muito longa trava a página inteira.
Web APIs — quem cuida do que é lento
setTimeout, fetch e eventos de clique não são executados pelo motor de JavaScript diretamente — são entregues ao navegador (ou ao Node), que cuida da espera por fora da call stack. Quando terminam, o resultado não entra direto na call stack: ele vai pra uma fila, esperando sua vez.
Duas filas: macrotasks e microtasks
Existem duas filas de espera, e elas não têm a mesma prioridade:
// vai pra fila de MACROTASKS (callback queue)
setTimeout(() => console.log("do setTimeout"), 0);
// vai pra fila de MICROTASKS
Promise.resolve().then(() => console.log("do then"));O event loop é o mecanismo que fica checando: "a call stack está vazia?". Quando está, ele primeiro esvazia toda a fila de microtasks — uma por uma, incluindo novas microtasks que apareçam no meio do processo — e só depois pega um único item da fila de macrotasks.
O exemplo que mostra tudo
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
console.log("4");
// ordem real de impressão: 1, 4, 3, 2"1" e "4" rodam na hora, de forma síncrona, porque estão direto no código principal. Quando a call stack fica vazia, o event loop esvazia a fila de microtasks primeiro — "3" — e só então pega o setTimeout da fila de macrotasks — "2". Isso acontece mesmo com 0 milissegundos de atraso no setTimeout: a fila de microtasks sempre tem prioridade.
await funciona sobre essa mesma base: o código depois de um await vira, por baixo, um .then() — ou seja, entra na fila de microtasks, não na de macrotasks.
Exercício
Qual é a ordem de impressão deste código, e por quê?console.log("A"); setTimeout(() => console.log("B"), 0); Promise.resolve().then(() => console.log("C")); Promise.resolve().then(() => console.log("D")); console.log("E");