Pular para o conteúdo
~/.primo-academy.sh
☰ Aulas · Real-time: WebSockets, SSE, Socket.IO, reconexao e presence · 0/7
Recomendado: essencial

Real-time no browser: polling, long-polling, SSE, WebSocket

9 min de leitura

fonte

Voce terminou o frontend e o backend. App funcionando, com REST API, fetch, e o modelo classico request/response. Mas ha uma categoria de apps que o REST nao atende: notificacoes em tempo real, chat, dashboards que atualizam ao vivo, colaboracao multi-user. O REST e' request/response: client pede, server responde. Mas e quando o server precisa mandar dados sem o client pedir? (chat nova mensagem, cotacao mudou, etc). O REST nao cobre isso.

Em 2026, ha 3 abordagens canonicas pra real-time no browser: polling, long- polling, Server-Sent Events (SSE) e WebSocket. Cada uma com tradeoffs diferentes de latencia, complexidade, e suporte. Saber quando usar cada e' o ponto de entrada pra real-time.

Se voce entende as 4 abordagens, a comparacao de tradeoffs (latencia, custo de conexao, complexidade, suporte), e o "quando usar cada" (notificacao one-way vs chat bidirecional vs dashboard real-time), voce sai de "como eu faco real-time?" pra "qual ferramenta resolve meu caso?".

O essencial 🟢

Real-time no browser em uma frase: o server precisa mandar dados pro client sem o client pedir, ou ambos trocam dados em alta frequencia. Diferente do REST classico (client pede, server responde).

Os 3 cenarios:

  1. Server-push (one-way): server manda pro client. Ex: notificacao de novo email, cotacao, log stream.
  2. Bidirecional: ambos mandam. Ex: chat, jogo multiplayer, colaboracao.
  3. Subscription-based: client "inscrito" em um canal, recebe updates. Ex: dashboard de metricas, status de job.

As 4 abordagens, em ordem cronologica:

AbordagemComo funcionaLatenciaCustoCaso de uso
PollingClient faz GET a cada N segundos1-NsAlto (requests constantes)Notificacao low-prio
Long PollingClient faz GET, server segura a response ate ter data0-100msMedioSubstitui polling, simples
SSEConexao HTTP persistente, server streama events0-100msBaixoOne-way push, log streams
WebSocketConexao full-duplex, bidirecional0-50msBaixoChat, games, colaboracao

Polling - "pergunta de novo em Ns". O pattern mais simples: setInterval chama fetch a cada X segundos.

setInterval(async () => {
  const data = await fetch("/api/notifications").then((r) => r.json());
  if (data.length > 0) {
    setNotifications(data);
  }
}, 5000);  // a cada 5s

Problemas:

  • Latencia minima = 5s (intervalo do poll). Pra real-time, ruim.
  • Custo alto: cada poll = request. 1000 users × 12 polls/min = 12000 req/min so pra "verificacao".
  • Bateria: celular acordando radio a cada 5s = bateria morre.

Use polling quando: notificacao low-prio que pode esperar minutos (atualizar contadores, status de background job) e a implementacao tem que ser simples (sem WebSocket server).

Long Polling - "server segura ate ter algo". O client faz request, mas o server NAO responde imediatamente - ele segura a conexao ate ter data para mandar (ou ate timeout).

async function longPoll() {
  while (true) {
    const res = await fetch("/api/poll");
    const data = await res.json();
    if (data) {
      handleData(data);
    }
    // Apos receber, abre nova conexao
  }
}

O server-side:

app.get("/api/poll", async (req, res) => {
  // Espera ate ter data OU 30s
  const data = await waitForNewData(30000);
  res.json(data);
});

Pro: latencia ~0 (assim que server tem data, manda). Contra: request HTTP constante (overhead de abrir/fechar conexao).

Use long polling quando: voce precisa de push-like behavior mas nao pode usar WebSocket/SSE (compat com IE11, proxy anti-websocket, etc).

Server-Sent Events (SSE) - "server empurra via HTTP". Conexao HTTP persistente (Content-Type: text/event-stream). Server streama events, client recebe via EventSource. One-way only (server → client).

// Client
const source = new EventSource("/api/events");

source.addEventListener("message", (e) => {
  const data = JSON.parse(e.data);
  handleData(data);
});

source.addEventListener("error", (e) => {
  // SSE reconecta automaticamente!
});
// Server
app.get("/api/events", (req, res) => {
  res.setHeader("Content-Type", "text/event-stream");
  res.setHeader("Cache-Control", "no-cache");
  res.setHeader("Connection", "keep-alive");

  const interval = setInterval(() => {
    res.write(`data: ${JSON.stringify(getData())}\n\n`);
  }, 1000);

  req.on("close", () => clearInterval(interval));
});

Vantagens do SSE:

  • Reconexao automatica (browser cuida).
  • Event types (event: foo\ndata: ...).
  • HTTP puro - funciona com qualquer proxy/firewall/CDN.
  • Last-Event-ID - re-conecta com header, server pode re-enviar eventos perdidos.

Use SSE quando: one-way push do server (notificacao, log stream, status de job, cotacao, dashboard "live"). NAO use SSE para chat bidirecional, jogos, colaboracao (esses precisam WebSocket).

WebSocket - "full duplex sobre TCP". Protocolo separado do HTTP (mas faz "upgrade" via HTTP na conexao inicial). Apos handshake, conexao persistente bidirecional - ambos podem mandar a qualquer momento.

// Client
const ws = new WebSocket("wss://api.example.com/ws");

ws.addEventListener("open", () => {
  console.log("Conectado");
});

ws.addEventListener("message", (event) => {
  const data = JSON.parse(event.data);
  handleData(data);
});

ws.send(JSON.stringify({ type: "ping" }));

Vantagens do WebSocket:

  • Latencia minima (0-50ms, dependendo de network).
  • Bidirecional - client e server trocam dados livremente.
  • Binario suportado (Blob, ArrayBuffer).
  • Eficiente - 1 conexao, sem overhead HTTP por mensagem.

Use WebSocket quando: chat, jogos, colaboracao, qualquer caso onde client tambem precisa mandar muito (nao so receber).

A decision tree: qual abordagem usar.

CasoRecomendadoPor que
Notificacao low-prio (1-5 min tolera)PollingMais simples, sem server complexo
Notificacao high-prio, 1-waySSEReconexao automatica, HTTP puro
Chat / mensagensWebSocketBidirecional, baixa latencia
Dashboard de metricas ao vivoSSEOne-way, simple
Multiplayer gameWebSocketLatencia minima + bidirecional
Log streaming / monitoringSSEText/event-stream nativo
Presence (cursor, online status)WebSocketBidirecional, presence events
Compat com IE11 / proxy antigoLong PollingFunciona em todo lugar
File transfer real-timeWebSocket (binario)Suporta blob/arraybuffer

Regra 2026: se voce precisa real-time, comece com SSE (one-way) ou WebSocket nativo (bidirecional). Se precisar de fallback automatico, rooms, ou ack, use Socket.IO (lib que abstrai tudo isso).

A "linha do tempo" do real-time. Em 2000-2010: polling e long-polling (limitado por HTTP). Em 2011: WebSocket API. Em 2015: SSE padronizado. Em 2018+: Socket.IO v3+. Em 2026: WebTransport (HTTP/3, ainda em rollout). Padrao moderno: SSE pra one-way, WebSocket pra bidirecional.

Aprofundamento 🟡

EventSource vs fetch + ReadableStream (custom SSE). EventSource e' a API nativa, mas tem limitacoes:

  • So GET.
  • Sem custom headers (cookie OK, mas Authorization: Bearer requer polyfill).
  • Sem request body.

Pra casos que precisam de custom headers ou POST, use fetch com ReadableStream:

const response = await fetch("/api/events", {
  headers: { "Authorization": `Bearer ${token}` },
});

const reader = response.body.getReader();
const decoder = new TextDecoder();

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const chunk = decoder.decode(value);
  // Parse SSE format manualmente
  for (const line of chunk.split("\n")) {
    if (line.startsWith("data: ")) {
      handleData(JSON.parse(line.slice(6)));
    }
  }
}

Mais codigo, mas controle total. Em 2026, o EventSource puro resolve 90% - use o custom so quando precisar.

WebSocket vs HTTP/2 streams vs WebTransport. HTTP/2 trouxe server push (server manda sem request), mas foi removido em Chrome desde 2022 (overhead, pouco uso). WebTransport e' o sucessor moderno: HTTP/3 + UDP + bidirecional, baixa latencia. Em 2026, suporte parcial (Chrome OK, Firefox 114+, Safari 17+). Para real-time moderno, WebSocket ainda e' o default estavel - WebTransport so faz sentido em apps de gaming/colaboracao de alta performance.

ReadableStream com cancelamento. Long-lived streams precisam de cleanup quando o usuario sai:

const controller = new AbortController();

async function stream() {
  const res = await fetch("/api/events", {
    signal: controller.signal,  // cancela quando controller.abort()
  });
  // ...
}

// Cleanup ao desmontar
useEffect(() => {
  return () => controller.abort();
}, []);

Sem cleanup, request fica aberta mesmo depois do componente desmontar, gerando "Cannot read property of undefined" em codigo que tenta usar o response.

EventSource polyfill. Pra IE11 (muitos sistemas corporativos ainda usam em 2026) e browsers antigos, use event-source-polyfill. Adiciona EventSource se nao existir, com pequenos shims (custom headers via query string).

WebSocket subprotocols. Alem de URL, WebSocket pode especificar subprotocol (via header Sec-WebSocket-Protocol):

// Client
const ws = new WebSocket("wss://api.example.com/ws", "graphql-ws");

// Server
const wss = new WebSocket.Server({
  noServer: true,
  handleProtocols: (protocols, request) => {
    if (protocols.has("graphql-ws")) return "graphql-ws";
    return false;  // rejeita
  },
});

Util pra graphql-ws (subscriptions GraphQL), wamp (Web Application Messaging Protocol), ou protocolos proprietarios. Em 2026, graphql-ws e' o caso mais comum.

Pra quem quer ir mais alem 🔴

EventSource reconexao automatica e Last-Event-ID. Quando o browser detecta que a conexao caiu, ele re-conecta automaticamente com o header Last-Event-ID (o ID do ultimo evento recebido). O server pode usar isso pra re-enviar eventos perdidos desde a desconexao:

// Server
app.get("/api/events", (req, res) => {
  const lastId = req.headers["last-event-id"];

  if (lastId) {
    // Re-envia eventos desde lastId ate agora
    const missed = events.filter((e) => e.id > lastId);
    for (const e of missed) {
      res.write(`id: ${e.id}\ndata: ${e.data}\n\n`);
    }
  }
  // Continua stream normal...
});

E o client precisa setar IDs nos eventos:

res.write(`id: ${event.id}\ndata: ${JSON.stringify(event.data)}\n\n`);

Sem isso, SSE perde eventos na reconexao. Cobre em sse com mais detalhes.

WebSocket compression (permessage- deflate). WebSocket suporta compressao por mensagem (RFC 7692). Reduz ate 80% do bandwidth. Suportado por todos os browsers modernos. Server (Node ws):

const wss = new WebSocket.Server({
  port: 8080,
  perMessageDeflate: true,  // habilita compressao
});

Cuidado: em redes lentas, compressao pode adicionar latencia. Em LAN, nao usar. Em mobile 3G/4G, sempre usar.

ping/pong - o heartbeat do WebSocket. WebSocket nao detecta conexao morta (NAT timeout, proxy timeout, etc). Ping/pong e' o "heartbeat" que detecta:

// Server
setInterval(() => {
  ws.ping();
}, 30000);  // ping a cada 30s

ws.on("pong", () => {
  ws.isAlive = true;
});

// Browser
ws.addEventListener("pong", () => {
  // marcar como vivo
});

Sem ping/pong, conexao "zumbi" parece viva mas nao manda dados. O usuario ve "conectado" mas nao recebe nada. Cobre em reconexao-e-estado.

Leitura recomendada:

Dica: o erro mais comum em real-time e' comecar com polling. "Vou fazer setInterval a cada 1s, simples". Em producao, mata a bateria do celular e aumenta a conta de servidor (mil requests/minuto por mil usuarios). Para one-way push, use SSE - reconexao automatica, 1 conexao persistente, suporte em todos os browsers modernos. Para bidirecional, use WebSocket. Polling so faz sentido pra latencia > 1min e implementacao simples (background jobs, status check).

No proximo no, vamos WebSocket nativo a fundo: a API completa, readyState e os eventos, mensagens texto/binario, e o state machine que governa o ciclo de vida de uma conexao.

// Quiz

Qual a diferenca fundamental entre SSE (Server-Sent Events) e WebSocket?

Escolha uma alternativa

// recursos

// avaliação da trilha

—
ainda sem avaliações