Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Long Polling no Node.js: atualizações em tempo real

Atualizado em: 24 de outubro de 2026

Rack de servidores processando fluxos de dados no Node.js

Long Polling no Node.js mantém uma requisição HTTP aberta até existir uma atualização ou até um timeout. Quando o servidor responde, o cliente abre outra requisição. A técnica oferece atualizações quase em tempo real em ambientes onde WebSocket ou Server-Sent Events não estão disponíveis.

O modelo continua usando HTTP convencional, proxies e autenticação já existentes. Em contrapartida, cada cliente pode manter uma conexão pendente, e a aplicação precisa controlar timeouts, cancelamento, memória, limites de concorrência e reconexões.

Fluxo básico

  1. o cliente envia GET /updates?after=cursor;
  2. o servidor consulta novidades;
  3. se existirem, responde imediatamente;
  4. se não existirem, aguarda um sinal ou timeout;
  5. o cliente processa e abre nova requisição.

Endpoint simples

app.get('/updates', async (req, res) => {
  const after = String(req.query.after ?? '');
  const userId = req.auth.userId;

  const updates = await repository.findAfter({ userId, after });
  if (updates.length > 0) {
    return res.json({ items: updates, cursor: updates.at(-1).id });
  }

  return waitForUpdates({ req, res, userId, after });
});

Aguardando com timeout

function waitForUpdates({ req, res, userId, after }) {
  const timeout = setTimeout(async () => {
    cleanup();
    res.status(204).end();
  }, 25_000);

  const unsubscribe = eventBus.subscribe(userId, async () => {
    const updates = await repository.findAfter({ userId, after });
    if (updates.length === 0 || res.headersSent) return;

    cleanup();
    res.json({ items: updates, cursor: updates.at(-1).id });
  });

  function cleanup() {
    clearTimeout(timeout);
    unsubscribe();
  }

  req.on('close', cleanup);
}

Sempre remova listeners quando o cliente desconecta ou o timeout ocorre. Caso contrário, a aplicação acumula memória e callbacks.

Não use apenas memória

Um EventEmitter local acorda somente clientes conectados à mesma instância. Em múltiplos processos ou pods, use um sinal compartilhado, como Redis Pub/Sub, broker ou PostgreSQL LISTEN/NOTIFY.

Consulte LISTEN e NOTIFY no Node.js.

Sinal versus estado

O sinal apenas informa que pode haver dados novos. O endpoint deve consultar uma fonte persistente usando o cursor. Assim, uma notificação perdida não perde a atualização.

Cursor estável

SELECT id, type, payload, created_at
FROM user_updates
WHERE user_id = $1
  AND id > $2
ORDER BY id ASC
LIMIT 100;

Use um ID monotônico ou cursor composto. Evite depender apenas de timestamp quando vários eventos podem ter o mesmo valor.

Cliente com reconexão

async function poll() {
  let delay = 0;

  while (!controller.signal.aborted) {
    try {
      if (delay) await sleep(delay, controller.signal);

      const response = await fetch(`/updates?after=${cursor}`, {
        signal: controller.signal
      });

      if (response.status === 204) {
        delay = 0;
        continue;
      }

      if (!response.ok) throw new Error(`HTTP ${response.status}`);

      const data = await response.json();
      applyUpdates(data.items);
      cursor = data.cursor;
      delay = 0;
    } catch (error) {
      if (controller.signal.aborted) break;
      delay = Math.min(30_000, Math.max(1_000, delay * 2 || 1_000));
    }
  }
}

Use jitter para muitos clientes não reconectarem ao mesmo tempo.

Timeouts de proxy

O timeout da aplicação deve ser menor que o timeout de load balancer, CDN ou proxy. Se o proxy fecha em 30 segundos, responda por volta de 20 a 25 segundos para encerrar de forma controlada.

Keep-Alive

Long polling abre muitas requisições sequenciais. HTTP Keep-Alive reduz custo de novas conexões. Consulte HTTP Keep-Alive no Node.js.

Limites por usuário

Um cliente defeituoso pode abrir várias polls simultâneas. Limite uma ou poucas conexões por usuário, sessão e IP.

if (activePolls.count(userId) >= 2) {
  return res.status(429).json({ error: 'Muitas requisições pendentes' });
}

Libere o contador em todos os caminhos de cleanup.

Autorização

Valide o acesso antes de esperar. O cursor não pode permitir que um usuário consulte eventos de outro. Inclua tenant e identidade na query.

Backpressure

Retorne lotes limitados. Se houver mais dados, o cliente repete imediatamente com o novo cursor.

{
  "items": [ ... ],
  "cursor": "18482",
  "hasMore": true
}

Não envie milhares de eventos em uma única resposta.

Eventos duplicados

O cliente pode receber o mesmo item após timeout ou falha de rede. Use IDs e processamento idempotente.

Cancelamento

Quando o cliente fecha a conexão, cancele consultas e operações relacionadas. O evento close ajuda, mas uma query em andamento pode exigir AbortSignal ou cancelamento do driver.

Graceful shutdown

Durante deploy, pare de aceitar novas polls e responda às existentes com 503 e Retry-After, ou encerre-as com resposta vazia para reconexão.

for (const response of activeResponses) {
  if (!response.headersSent) {
    response.setHeader('Retry-After', '2');
    response.status(503).end();
  }
}

Long Polling versus SSE

Server-Sent Events mantém uma única resposta e envia vários eventos, reduzindo overhead. Long polling funciona melhor quando infraestrutura interrompe streams longos ou quando a frequência de atualização é baixa.

Veja Server-Sent Events no Node.js.

Long Polling versus WebSocket

WebSocket oferece comunicação bidirecional persistente. Long polling é mais simples para atualizações ocasionais do servidor ao cliente e usa semântica HTTP normal.

Consulte WebSocket com Node.js.

Cache

Respostas de atualização por usuário não devem ser armazenadas por caches compartilhados.

res.setHeader('Cache-Control', 'no-store');

Observabilidade

Monitore polls ativas, tempo médio de espera, respostas com dados, timeouts vazios, desconexões, reconexões, erros e número de listeners. Um aumento repentino pode indicar loop de reconexão ou proxy encerrando cedo.

Testes

  • dados existentes respondem imediatamente;
  • novo evento acorda a poll;
  • timeout retorna 204;
  • cliente fecha conexão;
  • listener é removido;
  • duas instâncias recebem sinal;
  • cursor evita perda;
  • evento duplicado;
  • limite por usuário;
  • shutdown durante espera.

Erros comuns

  • manter listeners após desconexão;
  • usar apenas EventEmitter local;
  • não persistir atualizações;
  • timeout maior que o proxy;
  • não limitar conexões;
  • retornar lotes ilimitados;
  • usar timestamp instável;
  • não tratar duplicação;
  • cachear respostas privadas.

Checklist

  • cursor durável;
  • sinal compartilhado;
  • timeout controlado;
  • cleanup completo;
  • limite por usuário;
  • lotes pequenos;
  • reconexão com backoff;
  • autorização aplicada;
  • no-store;
  • shutdown gracioso.

Conclusão

Long Polling no Node.js oferece atualizações quase em tempo real usando HTTP convencional. A técnica é útil quando SSE e WebSocket não são opções ou quando eventos são pouco frequentes.

Use uma fonte persistente, cursor estável e um sinal compartilhado apenas para acordar a requisição. Com timeout, cleanup e limites por usuário, long polling pode operar de forma previsível mesmo em múltiplas instâncias.

Consulte a documentação HTTP do Node.js e o RFC de semântica HTTP.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita