O event loop do JavaScript
JavaScript é single-thread — mas executa código assíncrono. O event loop é o mecanismo que orquestra tudo isso, e entendê-lo explica comportamentos que parecem mágicos.
Um único thread, muitas tarefas
JavaScript é single-threaded — executa uma coisa de cada vez. Mas você pode fazer requests, ouvir cliques e usar timers sem travar. Como? O browser fornece Web APIs (setTimeout, fetch, DOM events) que são executadas fora do thread JS e, quando prontas, adicionam callbacks a filas.
O event loop monitora essas filas: quando a Call Stack esvazia, ele pega a próxima tarefa. Mas há duas filas com prioridades diferentes: Microtask Queue (Promises, await) tem prioridade e é esvaziada completamente antes de qualquer Task Queue (setTimeout, eventos DOM).
setTimeout(fn, 0) ou scheduler.postTask() para criar uma macrotask.
Veja o event loop em ação
Escolha um snippet, clique em Próximo passo para avançar ou Executar para animar automaticamente. Observe como itens se movem entre Call Stack, Web APIs e filas.
console.log("A");
setTimeout(() => {
console.log("C");
}, 0);
console.log("B");O loop em pseudocódigo
// O Event Loop em pseudocódigo while (true) { // 1. Executar a tarefa atual (macrotask) executar(taskQueue.shift()); // 2. Esvaziar TODA a microtask queue while (microtaskQueue.length) { executar(microtaskQueue.shift()); } // 3. Se necessário, renderizar (a cada ~16ms) if (precisaRenderizar()) renderizar(); } // Fontes de macrotasks (Task Queue): setTimeout(fn, 0) // mínimo ~4ms no browser setInterval(fn, 100) fetch().then() // NÃO — fetch callback é microtask! eventos DOM (click, keydown…) // Fontes de microtasks (Microtask Queue): Promise.resolve().then(fn) queueMicrotask(fn) await (dentro de async function)
Evolução do código assíncrono
// Três formas de código assíncrono // 1. Callbacks (antigo) — "callback hell" setTimeout(() => { fetch(url, (dados) => { processar(dados, (resultado) => { salvar(resultado); // pirâmide da desgraça }); }); }, 1000); // 2. Promises — encadeável, tratamento de erro claro fetch(url) .then(r => r.json()) .then(dados => processar(dados)) .catch(e => console.error(e)); // 3. async/await — síncrono de ler, assíncrono de executar async function buscar() { try { const dados = await fetch(url).then(r => r.json()); return processar(dados); } catch (e) { console.error(e); } }
await promise suspende a função
atual e devolve o controle ao event loop. O thread continua livre para executar outros
callbacks. Quando a promise resolve, o event loop agenda a continuação como uma microtask.
Isso explica por que código após await não executa "imediatamente" — ele vai
para a microtask queue primeiro.
Implemente um scheduler simples
Mini projeto: implemente uma fila de tarefas com prioridade usando queueMicrotask e setTimeout. Execute 5 tarefas de alta prioridade (microtask) intercaladas com 5 de baixa (macrotask). Observe e documente a ordem de execução.
Projeto principal: construa um debounce e um throttle do zero. Debounce aguarda que o usuário pare de digitar antes de chamar a função. Throttle limita chamadas a uma por intervalo. Use setTimeout/clearTimeout.
Desafio extra: implemente Promise.all, Promise.race e Promise.allSettled do zero usando o construtor new Promise((resolve, reject) => {}). Use seus implementações para buscar 3 APIs em paralelo.
Teste sua intuição
console.log("início"); setTimeout(()=>console.log("T"),0); Promise.resolve().then(()=>console.log("P")); console.log("fim");
— qual é a saída?
await?
Onde você encontra isso
Node.js
Node.js usa o mesmo event loop (via libuv). Operações de I/O (arquivo, rede) nunca bloqueiam o thread — elas delegam para o sistema operacional e recebem callbacks. É por isso que Node serve milhares de conexões simultâneas com um único thread.
React Concurrent Mode
O React 18 usa scheduler.postTask() para dividir o trabalho de render em pedaços menores, cedendo ao browser para renderizar entre eles. Isso elimina o "jank" em árvores de componentes grandes.
Web Workers
Para trabalho CPU-intensivo (parsear CSV grande, processar imagens), use Web Workers — threads separados que não bloqueiam o thread principal. Comunicam via postMessage.
WebSockets e SSE
Conexões em tempo real (chat, preço ao vivo) usam callbacks de eventos DOM (ws.onmessage). Cada mensagem entra na Task Queue e processa no próximo ciclo do event loop — sem polling.