voltar aos artigosfunctions & execution
$cd~/artigos/event-loop-como-javascript-executa-assincrono
Functions & Execution
série 33 Conceitos JS·parte 12 de 38

Event Loop: Como o JavaScript Faz Múltiplas Coisas Sendo Single-Threaded

16 minVanderlei José Alamino

Como o JavaScript lida com múltiplas coisas ao mesmo tempo quando só consegue fazer uma coisa de cada vez? Por que este código imprime em uma ordem surpreendente?

snippetJavaScript
console.log('Start');setTimeout(() => console.log('Timeout'), 0);Promise.resolve().then(() => console.log('Promise'));console.log('End'); // Saída:// Start// End// Promise// Timeout

Mesmo com delay de 0ms, Timeout imprime por último. A resposta está no event loop — o mecanismo do JavaScript para lidar com operações assíncronas mantendo-se single-threaded.

Neste guia, você vai aprender:

  • Por que o JavaScript precisa de um event loop (e o que "single-threaded" realmente significa)
  • Como o setTimeout REALMENTE funciona (spoiler: o delay NÃO é garantido!)
  • A diferença entre tasks e microtasks (e por que isso importa)
  • Por que Promise.then() roda antes de setTimeout(..., 0)
  • Como usar setTimeout, setInterval e requestAnimationFrame de forma eficaz
  • Perguntas clássicas de entrevista explicadas passo a passo

Pré-requisitos: Este guia assume familiaridade com a call stack e com Promises. Se esses conceitos são novos para você, leia-os primeiro!

event-loop-como-javascript-executa-assincrono.meta.jsonjson

// metadados do artigo

{
  "tag": "functions & execution",
  "readTime": "16 min",
  "author": "Vanderlei José Alamino",
  "source": "33jsconcepts.com",
  "series": "33 Conceitos JS",
  "part": {
    "current": 12,
    "total": 38
  }
}
abrir fonte original
event-loop-como-javascript-executa-assincrono.mdmarkdown

Fonte de referência

Conteúdo adaptado a partir do guia original em inglês.

33jsconcepts.com
01

seção

A Cozinha do Restaurante: Uma Analogia do Mundo Real

O event loop é o mecanismo do JavaScript para executar código, lidar com eventos e gerenciar operações assíncronas. Conforme definido no WHATWG HTML Living Standard, ele coordena a execução verificando filas de callbacks quando a call stack está vazia, empurrando tarefas enfileiradas para a pilha. Isso permite comportamento não bloqueante apesar do JavaScript ser single-threaded.

Imagine uma cozinha de restaurante movimentada com um único chef que só consegue cozinhar um prato por vez. Apesar dessa limitação, o restaurante atende centenas de clientes porque a cozinha tem um sistema inteligente:

snippettext
A COZINHA DO JAVASCRIPT                                         ┌─────────────────────────┐┌────────────────────────────────┐       │    TIMERS DA COZINHA    ││         PICO DE PEDIDOS        │       │       (Web APIs)        ││        (Call Stack)            │       │                         ││  ┌──────────────────────────┐  │       │  [Timer: 3 min - sopa]  ││  │  Cozinhando agora:       │  │       │  [Timer: 10 min - assado]││  │  "sanduíche grelhado"    │  │       │  [Aguardando: entrega]  ││  ├──────────────────────────┤  │       │                         ││  │  Próximo: "preparar salada"│ │       └───────────┬─────────────┘│  └──────────────────────────┘  │                   │└────────────────────────────────┘                   │ (timer pronto!)          ▲                                          ▼          │                          ┌──────────────────────────────┐          │                          │    JANELA "PEDIDO PRONTO!"   │    GERENTE DA COZINHA             │        (Task Queue)          │     (Event Loop)                   │                              │                                     │  [sopa pronta] [entrega aqui]│    "Chef livre? ───────────────────►│                              │     Aqui está o próximo pedido!"   └──────────────────────────────┘          │                                          ▲          │                          ┌───────────────┴──────────────┐          │                          │      PEDIDOS VIP URGENTES    │          └──────────────────────────│      (Microtask Queue)       │             (VIP primeiro!)         │                              │                                     │  [empratamento] [guarnição]  │                                     └──────────────────────────────┘

Como isso mapeia para o JavaScript:

CozinhaJavaScript
Chef ÚnicoEngine JavaScript (single-threaded)
Pico de PedidosCall Stack (trabalho atual, LIFO)
Timers da CozinhaWeb APIs (setTimeout, fetch, etc.)
Janela "Pedido Pronto!"Task Queue (callbacks aguardando)
Pedidos VIP UrgentesMicrotask Queue (promises, alta prioridade)
Gerente da CozinhaEvent Loop (coordenador)

O chef (JavaScript) só consegue trabalhar em um prato (tarefa) por vez. Mas os timers da cozinha (Web APIs) rodam de forma independente! Quando um timer dispara, o prato vai para a janela "Pedido Pronto!" (Task Queue). O gerente (Event Loop) verifica constantemente: "O chef está livre? Aqui está o próximo pedido!"

Pedidos VIP (Promises) sempre têm prioridade. Eles passam na frente dos pedidos regulares na fila.

TL;DR: O JavaScript é single-threaded, mas alcança concorrência delegando trabalho para APIs do navegador, que rodam em segundo plano. Quando terminam, os callbacks vão para filas. O Event Loop move callbacks das filas para a call stack quando ela está vazia.

02

seção

O Problema: JavaScript é Single-Threaded

O JavaScript só consegue fazer uma coisa por vez. Há uma call stack, um thread de execução.

snippetJavaScript
// JavaScript executa estes UM POR VEZ, em ordemconsole.log('Primeiro');   // 1. Isso rodaconsole.log('Segundo');  // 2. Depois issoconsole.log('Terceiro');   // 3. Depois isso

Por Que Isso é um Problema?

Imagine se cada operação bloqueasse o programa inteiro. Considere a Fetch API:

snippetJavaScript
// Se fetch() fosse síncrono (bloqueante)...const data = fetch('https://api.example.com/data'); // Leva 2 segundosconsole.log(data);// NADA mais pode acontecer por 2 segundos!// - Sem clicar em botões// - Sem rolar a página// - Sem animações// - UI completamente congelada!

Uma chamada de API de 30 segundos congelaria sua página inteira por 30 segundos. Os usuários pensariam que o navegador travou! Segundo a pesquisa Core Web Vitals do Google, qualquer interação que leva mais de 200 milissegundos para responder é percebida como lenta pelos usuários.

A Solução: JavaScript Assíncrono

O JavaScript resolve isso delegando tarefas demoradas ao navegador (ou Node.js), que as trata em segundo plano. Funções como setTimeout() não bloqueiam:

snippetJavaScript
console.log('Start'); // Isso não bloqueia! O navegador cuida do timersetTimeout(() => {  console.log('Timer concluído');}, 2000); console.log('End'); // Saída:// Start// End// Timer concluído (após 2 segundos)

O ingrediente secreto que torna isso possível? O Event Loop.

03

seção

O Ambiente de Runtime do JavaScript

Para entender o Event Loop, você precisa ver o quadro completo:

snippettext
┌─────────────────────────────────────────────────────────────────────────┐│                        RUNTIME DO JAVASCRIPT                            ││  ┌─────────────────────────────────────────────────────────────────┐   ││  │              ENGINE JAVASCRIPT (V8, SpiderMonkey, etc.)         │   ││  │  ┌───────────────────────┐    ┌───────────────────────────┐     │   ││  │  │      CALL STACK       │    │          HEAP             │     │   ││  │  │  ┌─────────────────┐  │    │   { objetos armazenados } │     │   ││  │  │  │ processData()   │  │    │   [ arrays armazenados ]  │     │   ││  │  │  ├─────────────────┤  │    │   referências de funções  │     │   ││  │  │  │ fetchUser()     │  │    │                           │     │   ││  │  │  ├─────────────────┤  │    │                           │     │   ││  │  │  │ main()          │  │    │                           │     │   ││  │  │  └─────────────────┘  │    └───────────────────────────┘     │   ││  │  └───────────────────────┘                                      │   ││  └─────────────────────────────────────────────────────────────────┘   ││                                                                         ││  ┌─────────────────────────────────────────────────────────────────┐   ││  │                    APIs DO NAVEGADOR / NODE.js                   │   ││  │   setTimeout()  setInterval()  fetch()  eventos DOM              │   ││  │   requestAnimationFrame()  IndexedDB  WebSockets                 │   ││  │   (Tratadas FORA da execução JavaScript!)                        │   ││  └─────────────────────────────────────────────────────────────────┘   ││                                    │ callbacks                          ││                                    ▼                                    ││  ┌──────────────────────────────────────────────────────────────────┐  ││  │  MICROTASK QUEUE                    TASK QUEUE (Macrotask)       │  ││  │  Promise.then()                     setTimeout callback            │  ││  │  queueMicrotask()                   setInterval callback           │  ││  │  MutationObserver                   callbacks de I/O               │  ││  │  async/await (depois)               handlers de eventos UI         │  ││  │         ▲ HIGHER PRIORITY                    ▲                   │  ││  └─────────┼────────────────────────────────────┼───────────────────┘  ││            └──────────┬─────────────────────────┘                       ││                       │                                                 ││              ┌────────┴────────┐                                        ││              │   EVENT LOOP    │                                        ││              │  "Call stack    ├──────────► Empurra próximo callback   ││              │   vazia?"       │            para a call stack           ││              └─────────────────┘                                        │└─────────────────────────────────────────────────────────────────────────┘

Os Componentes

Call Stack

A Call Stack é onde o JavaScript rastreia qual função está rodando. É uma estrutura LIFO (Last In, First Out), como uma pilha de pratos.

snippetJavaScript
function multiply(a, b) { return a * b; }function square(n) { return multiply(n, n); }function printSquare(n) {  const result = square(n);  console.log(result);}printSquare(4);

Heap

O Heap é uma região grande e não estruturada de memória onde objetos, arrays e funções são armazenados.

snippetJavaScript
const user = { name: 'Alice' };  // Objeto no heapconst numbers = [1, 2, 3];       // Array no heap

Web APIs (Navegador) / APIs C++ (Node.js)

Estas NÃO fazem parte do JavaScript em si! São fornecidas pelo ambiente: setTimeout, setInterval, fetch, eventos DOM, requestAnimationFrame, sistema de arquivos (Node.js), etc. São tratadas pelo runtime fora da execução JavaScript, permitindo que o JavaScript permaneça não bloqueante.

Task Queue (Fila de Macrotasks)

A Task Queue guarda callbacks de: setTimeout, setInterval, operações de I/O, tarefas de renderização UI, handlers de eventos. Tarefas são processadas uma por vez, com possível renderização entre elas.

Microtask Queue

A Microtask Queue guarda callbacks de alta prioridade: Promise.then(), .catch(), .finally(), queueMicrotask(), MutationObserver, código após await em funções async. Microtasks SEMPRE rodam antes da próxima task! A fila inteira de microtasks é esvaziada antes de mover para a task queue.

Event Loop

O Event Loop é o orquestrador. Seu trabalho é simples mas crucial:

snippettext
PARA SEMPRE:  1. Executar todo código na Call Stack até ficar vazia  2. Executar TODAS as microtasks (até a fila esvaziar)  3. Renderizar se necessário (atualizar a UI)  4. Pegar UMA task da task queue  5. Voltar ao passo 1

A ideia-chave: microtasks podem "fomear" a task queue! Se microtasks continuam adicionando mais microtasks, tasks (e renderização) nunca têm chance de rodar.

04

seção

Como o Event Loop Funciona: Passo a Passo

Vamos rastrear alguns exemplos para ver o event loop em ação.

Exemplo 1: setTimeout Básico

snippetJavaScript
console.log('Start'); setTimeout(() => {  console.log('Timeout');}, 0); console.log('End');

Saída: Start, End, Timeout

Passo 1: Executar console.log('Start')

snippettext
Call Stack: [console.log('Start')]Web APIs: []Task Queue: []Saída: "Start"

Passo 2: Executar setTimeout()

setTimeout é chamado → registra timer nas Web APIs → retorna imediatamente

snippettext
Call Stack: []Web APIs: [Timer: 0ms → callback]Task Queue: []

O timer é tratado pelo navegador, NÃO pelo JavaScript!

Passo 3: Timer completa (0ms)

snippettext
Call Stack: []Web APIs: []Task Queue: [callback]

Passo 4: Executar console.log('End')

Mas espere! Ainda estamos executando o script principal!

snippettext
Call Stack: [console.log('End')]Task Queue: [callback]Saída: "Start", "End"

Passo 5: Script principal completo, Event Loop verifica filas

snippettext
Call Stack: [callback]Task Queue: []Saída: "Start", "End", "Timeout"

Mesmo com delay de 0ms, o callback do setTimeout NUNCA roda imediatamente. Ele deve esperar: (1) o script atual terminar, (2) todas as microtasks completarem, (3) sua vez na task queue.

Exemplo 2: Promises vs setTimeout

snippetJavaScript
console.log('1'); setTimeout(() => console.log('2'), 0); Promise.resolve().then(() => console.log('3')); console.log('4');

Saída: 1, 4, 3, 2

Por que 3 vem antes de 2?

snippettext
Código síncrono roda primeiro:  "1" e "4" impressosMicrotask Queue: [Promise callback]Task Queue: [setTimeout callback]Call stack vazia → Event Loop verifica Microtask Queue primeiro  Promise callback roda → "3"Microtask queue vazia → Event Loop pega da Task Queue  setTimeout callback roda → "2"Saída final: "1", "4", "3", "2"

A Regra de Ouro: Microtasks (Promises) SEMPRE rodam antes de Macrotasks (setTimeout), independentemente de qual foi agendada primeiro.

Exemplo 3: Microtasks Aninhadas

snippetJavaScript
console.log('Start'); Promise.resolve()  .then(() => {    console.log('Promise 1');    Promise.resolve().then(() => console.log('Promise 2'));  }); setTimeout(() => console.log('Timeout'), 0); console.log('End');

Saída: Start, End, Promise 1, Promise 2, Timeout

Mesmo que a segunda promise seja criada DEPOIS do setTimeout ser registrado, ela ainda roda primeiro porque a fila inteira de microtasks deve ser esvaziada antes de qualquer task rodar!

05

seção

Tasks vs Microtasks: O Quadro Completo

O Que Cria Tasks (Macrotasks)?

FonteDescrição
setTimeout(fn, delay)Roda fn após pelo menos delay ms
setInterval(fn, delay)Roda fn repetidamente a cada ~delay ms
Callbacks de I/ORespostas de rede, leituras de arquivo
Eventos UIclick, scroll, keydown, mousemove
setImmediate(fn)Só Node.js, roda após I/O
MessageChannelCallbacks de postMessage

E o requestAnimationFrame? rAF NÃO é uma task. Roda durante a fase de renderização, após microtasks mas antes do navegador pintar. Coberto em detalhes na seção de Timers.

O Que Cria Microtasks?

FonteDescrição
Promise.then/catch/finallyQuando a promise é resolvida
async/awaitCódigo após await
queueMicrotask(fn)Enfileira microtask explicitamente
MutationObserverQuando o DOM muda

O Algoritmo do Event Loop (Simplificado)

snippetJavaScript
// Pseudocódigo do Event Loop (conforme especificação HTML)while (true) {  // 1. Processar UMA task da task queue (se disponível)  if (taskQueue.hasItems()) {    const task = taskQueue.dequeue();    execute(task);  }   // 2. Processar TODAS as microtasks (até a fila esvaziar)  while (microtaskQueue.hasItems()) {    const microtask = microtaskQueue.dequeue();    execute(microtask);    // Novas microtasks adicionadas durante execução também são processadas!  }   // 3. Renderizar se necessário (~60fps)  if (shouldRender()) {    runAnimationFrameCallbacks();    render();  }}

Fome de Microtasks: Se microtasks continuam adicionando mais microtasks, a task queue (e a renderização!) nunca terá chance de rodar:

snippetJavaScript
// NÃO FAÇA ISSO - loop infinito de microtasks!function forever() {  Promise.resolve().then(forever);}forever(); // Navegador congela!
06

seção

Timers do JavaScript: setTimeout, setInterval e requestAnimationFrame

Agora que você entende o event loop, vamos mergulhar nas funções de temporização do JavaScript.

setTimeout: Execução Atrasada Única

snippetJavaScript
// Sintaxeconst timerId = setTimeout(callback, delay, ...args);clearTimeout(timerId); // Cancelar antes de rodar // Uso básicosetTimeout(() => {  console.log('Olá após 2 segundos!');}, 2000); // Passar argumentossetTimeout((name, greeting) => {  console.log(`${greeting}, ${name}!`);}, 1000, 'Alice', 'Olá');

O Mito do "Delay Zero"

setTimeout(fn, 0) NÃO roda imediatamente!

snippetJavaScript
console.log('A');setTimeout(() => console.log('B'), 0);console.log('C'); // Saída: A, C, B (NÃO A, B, C!)

A Regra dos 4ms

Após 5 timeouts aninhados, navegadores impõem delay mínimo de 4ms:

snippetJavaScript
let start = Date.now();let times = []; setTimeout(function run() {  times.push(Date.now() - start);  if (times.length < 10) setTimeout(run, 0);  else console.log(times);}, 0);// Típico: [1, 1, 1, 1, 4, 9, 14, 19, 24, 29]

O delay do setTimeout é um MÍNIMO, não uma garantia! Se a call stack está ocupada, o callback deve esperar.

setInterval: Execução Repetida

snippetJavaScript
let count = 0;const intervalId = setInterval(() => {  count++;  console.log(`Count: ${count}`);  if (count >= 5) {    clearInterval(intervalId);    console.log('Done!');  }}, 1000);

O Problema de Drift do setInterval

setInterval não considera o tempo de execução do callback. Se o callback leva 300ms e o intervalo é 1000ms, o tempo entre o FIM de um e o INÍCIO do próximo é apenas 700ms.

Solução: setTimeout Aninhado

snippetJavaScript
function preciseInterval(callback, delay) {  function tick() {    callback();    setTimeout(tick, delay); // Agenda próximo DEPOIS do atual completar  }  setTimeout(tick, delay);}

Quando usar qual:

  • setInterval: Atualizações simples de UI que não dependem da execução anterior
  • setTimeout aninhado: Operações sequenciais, polling de API, quando precisão importa

requestAnimationFrame: Animações Suaves

requestAnimationFrame (rAF) sincroniza com a taxa de atualização do navegador (geralmente 60fps = ~16,67ms por frame).

snippetJavaScript
function animate(timestamp) {  element.style.left = (timestamp / 10) + 'px';  requestAnimationFrame(animate);}requestAnimationFrame(animate);
RecursosetTimeout/setIntervalrequestAnimationFrame
Sincroniza com displayNãoSim
Eficiente em bateriaNãoSim (pausa em abas em background)
Animações suavesPode ser irregularOtimizado pelo navegador
Uso de CPURoda mesmo com aba ocultaPausa quando aba oculta

Quando o requestAnimationFrame Roda

snippettext
Uma Iteração do Event Loop:┌─────────────────────────────────────────────────────────────────┐│ 1. Rodar task da Task Queue                                     ││ 2. Rodar TODAS as microtasks                                    ││ 3. Se hora de renderizar:                                       ││    a. Rodar callbacks requestAnimationFrame  ← AQUI!              ││    b. Renderizar/pintar a tela                                  ││ 4. Se tempo ocioso antes do próximo frame:                      ││    Rodar callbacks requestIdleCallback                          │└─────────────────────────────────────────────────────────────────┘
07

seção

Perguntas Clássicas de Entrevista

08

seção

Conceitos Errados Comuns

"setTimeout(fn, 0) roda imediatamente"

Errado! Mesmo com delay de 0ms, o callback vai para a Task Queue e deve esperar o script atual completar, todas as microtasks esvaziarem e sua vez na fila.

snippetJavaScript
setTimeout(() => console.log('timeout'), 0);Promise.resolve().then(() => console.log('promise'));console.log('sync');// Saída: sync, promise, timeout

"O delay do setTimeout é garantido"

Errado! O delay é um tempo MÍNIMO de espera, não uma garantia. Se a call stack está ocupada ou há items na frente na Task Queue, o delay real será maior.

"JavaScript é assíncrono"

Parcialmente errado! O JavaScript em si é single-threaded e síncrono. O comportamento assíncrono vem do ambiente de runtime (navegador/Node.js), Web APIs que rodam em threads separadas, e o Event Loop que coordena callbacks.

"O Event Loop faz parte do JavaScript"

Errado! O Event Loop NÃO está definido na especificação ECMAScript. Está definido na especificação HTML (para navegadores) e implementado pelo ambiente de runtime. Navegadores, Node.js e Deno têm implementações diferentes.

"setInterval é preciso"

Errado! setInterval pode sofrer drift, pular callbacks ou ter timing inconsistente. Se um callback leva mais que o intervalo, callbacks se acumulam. Navegadores podem limitar timers em abas em background. Para timing preciso, use setTimeout aninhado ou requestAnimationFrame.

09

seção

Bloqueando o Event Loop

O Que Acontece Quando Você Bloqueia?

Quando código síncrono roda por muito tempo, TUDO para:

snippetJavaScript
button.addEventListener('click', () => {  for (let i = 0; i < 10000000000; i++) {    // ... computação pesada  }});

Consequências: UI congela, animações param, callbacks de setTimeout/setInterval atrasam, promises não resolvem, página fica irresponsiva.

Soluções

Web Workers

Mova computação pesada para uma thread separada:

snippetJavaScript
// main.jsconst worker = new Worker('worker.js');worker.postMessage({ data: largeArray });worker.onmessage = (event) => console.log('Result:', event.data); // worker.jsself.onmessage = (event) => {  const result = heavyComputation(event.data);  self.postMessage(result);};

Chunking com setTimeout

snippetJavaScript
function processInChunks(items, process, chunkSize = 100) {  let index = 0;  function doChunk() {    const end = Math.min(index + chunkSize, items.length);    for (; index < end; index++) process(items[index]);    if (index < items.length) setTimeout(doChunk, 0);  }  doChunk();}

requestIdleCallback

snippetJavaScript
function doNonCriticalWork(deadline) {  while (deadline.timeRemaining() > 0 && tasks.length > 0) {    tasks.shift()();  }  if (tasks.length > 0) requestIdleCallback(doNonCriticalWork);}requestIdleCallback(doNonCriticalWork);
10

seção

Renderização e o Event Loop

Onde a Renderização se Encaixa?

O navegador tenta renderizar a 60fps (a cada ~16,67ms). A renderização acontece entre tasks, após microtasks:

snippettext
┌─────────────────────────────────────────────────────┐│                 Um Frame (~16,67ms)                 │├─────────────────────────────────────────────────────┤│  1. Task (da Task Queue)                            ││  2. Todas as Microtasks                             ││  3. Callbacks requestAnimationFrame                 ││  4. Cálculo de estilo                               ││  5. Layout                                          ││  6. Paint                                           ││  7. Composite                                       │└─────────────────────────────────────────────────────┘

Por Que 60fps Importa

FPSTempo por FrameExperiência
6016,67msSuave, responsivo
3033,33msLag perceptível
1566,67msMuito irregular
< 10> 100msInutilizável

Se seu JavaScript leva mais de ~16ms, você perde frames e a UI fica irregular.

Usando requestAnimationFrame para Atualizações Visuais

snippetJavaScript
// Ruim: padrão read-write-read força múltiplos layoutsconsole.log(element.offsetWidth);element.style.width = '100px';console.log(element.offsetHeight);element.style.height = '200px'; // Bom: agrupe leituras, adie escritas para rAFconst width = element.offsetWidth;const height = element.offsetHeight;requestAnimationFrame(() => {  element.style.width = width + 100 + 'px';  element.style.height = height + 100 + 'px';});
11

seção

Bugs e Armadilhas Comuns

1. Esquecer de clearInterval

snippetJavaScript
// BUG: vazamento de memória!function startPolling() {  setInterval(() => fetchData(), 5000);}startPolling();startPolling(); // Agora 2 intervals rodando! // FIX: armazene e limpelet pollInterval;function startPolling() {  stopPolling();  pollInterval = setInterval(fetchData, 5000);}function stopPolling() {  if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }}

2. Race Conditions com setTimeout

snippetJavaScript
// BUG: respostas podem chegar fora de ordemsearchInput.addEventListener('input', () => {  setTimeout(() => {    fetch(`/search?q=${searchInput.value}`).then(res => displayResults(res));  }, 300);}); // FIX: debounce — cancele timeout anteriorlet timeoutId;searchInput.addEventListener('input', () => {  clearTimeout(timeoutId);  timeoutId = setTimeout(() => {    fetch(`/search?q=${searchInput.value}`).then(res => displayResults(res));  }, 300);});

3. Binding de this em Callbacks de Timer

snippetJavaScript
// BUG: 'this' está erradoconst obj = {  name: 'Alice',  greet() {    setTimeout(function() {      console.log(`Hello, ${this.name}`); // undefined!    }, 100);  }}; // FIX: arrow functionconst obj1 = {  name: 'Alice',  greet() {    setTimeout(() => console.log(`Hello, ${this.name}`), 100); // "Alice"  }};

4. Problemas de Closure em Loops

snippetJavaScript
// BUG: todos callbacks veem valor finalfor (var i = 0; i < 3; i++) {  setTimeout(() => console.log(i), 100);}// Saída: 3, 3, 3 // FIX: use letfor (let i = 0; i < 3; i++) {  setTimeout(() => console.log(i), 100);}// Saída: 0, 1, 2

5. Assumir Precisão de Timer

snippetJavaScript
// BUG: assumir timing exatosetTimeout(() => {  const elapsed = Date.now() - start;  console.log(`Exatamente 1000ms? ${elapsed === 1000}`); // Quase sempre false!}, 1000); // REALIDADE: sempre permita variânciaconst tolerance = 50;setTimeout(() => {  const withinTolerance = Math.abs(Date.now() - start - 1000) <= tolerance;  console.log(`Dentro da tolerância? ${withinTolerance}`);}, 1000);
12

seção

Ferramenta de Visualização: Loupe

A melhor forma de realmente entender o Event Loop é vê-lo em ação.

Loupe — Visualizador do Event Loop

Criado por Philip Roberts (autor da famosa palestra "What the heck is the event loop anyway?" do JSConf EU, vista mais de 8 milhões de vezes). Esta ferramenta permite escrever código JavaScript e observar como ele se move pela call stack, Web APIs e fila de callbacks em tempo real.

Experimente este código no Loupe:

snippetJavaScript
console.log('Start'); setTimeout(function timeout() {  console.log('Timeout');}, 2000); Promise.resolve().then(function promise() {  console.log('Promise');}); console.log('End');

Observe como:

  1. Código síncrono roda primeiro
  1. setTimeout vai para Web APIs
  1. Callback da Promise vai para microtask queue
  1. Microtasks rodam antes do callback do timeout

Palestra recomendada: A talk de Philip Roberts no JSConf EU é um must-watch para desenvolvedores JavaScript que querem entender o event loop de forma visual e intuitiva.

$ summary --quick

Resumo rápido

  • JavaScript é single-threaded — Só uma coisa roda por vez na call stack
  • O Event Loop habilita async — Coordena entre call stack e filas de callbacks
  • Web APIs rodam em threads separadas — Timers, requisições de rede e eventos são tratados pelo navegador
  • Microtasks > Tasks — Callbacks de Promise SEMPRE rodam antes de callbacks de setTimeout
  • Delay do setTimeout é mínimo — Timing real depende do estado da call stack e das filas
  • setInterval pode sofrer drift — Use setTimeout aninhado para timing preciso
  • requestAnimationFrame para animações — Sincroniza com refresh rate do navegador, pausa em background
  • Nunca bloqueie a thread principal — Operações síncronas longas congelam toda a UI
  • Microtasks podem fomear tasks — Loops infinitos de microtasks impedem renderização
  • O Event Loop não é JavaScript — Faz parte do ambiente de runtime (navegador/Node.js)

Créditos da série

Este artigo faz parte de uma série de estudos inspirada no projeto 33 Conceitos JS, criado por Leonardo Maldonado. Todo o conteúdo original está disponível em 33jsconcepts.com.