Long Polling

Long Polling — техника эмуляции push-уведомлений через HTTP: клиент отправляет запрос и сервер держит соединение открытым до появления новых данных.

Зачем нужно

Настоящий WebSocket требует специальной поддержки на сервере и инфраструктуре. Long Polling работает поверх обычного HTTP и совместим с любым прокси и CDN. Это простейший способ получить «почти реальное время» без смены протокола.

Где используется

  • Чат-приложения до появления WebSocket (раннее поколение)
  • Системы уведомлений, где WebSocket избыточен
  • Среды с ограниченной инфраструктурой (нет WebSocket-поддержки на прокси)
  • Fallback-транспорт в библиотеках (Socket.io использует long polling как запасной)

Сравнение техник real-time

Техника Направление Протокол Когда выбирать
Short Polling клиент→сервер (интервал) HTTP Редкие обновления, просто
Long Polling клиент→сервер (удержание) HTTP Нет WS-поддержки
SSE сервер→клиент HTTP Только push, нет диалога
WebSocket двустороннее WS Чат, игры, real-time

Реализация

Клиент (JavaScript)

async function longPoll(url, onMessage, signal) {
  while (!signal?.aborted) {
    try {
      const res = await fetch(url, {
        signal,
        // Сервер ответит только при появлении данных (или по таймауту)
      });

      if (res.ok) {
        const data = await res.json();
        onMessage(data);
      }
    } catch (err) {
      if (err.name === 'AbortError') break;
      // При сетевой ошибке — небольшая пауза перед повтором
      await new Promise(r => setTimeout(r, 1000));
    }
    // Сразу снова отправляем запрос
  }
}

// Использование
const controller = new AbortController();
longPoll('/api/notifications', (data) => {
  console.log('Новое уведомление:', data);
}, controller.signal);

// Остановить при уходе со страницы
window.addEventListener('beforeunload', () => controller.abort());

Сервер (Node.js / Express)

const pending = new Map(); // userId → res

app.get('/api/notifications', (req, res) => {
  const userId = req.user.id;

  // Сохраняем объект ответа — не отвечаем сразу
  pending.set(userId, res);

  // Таймаут: если нет данных за 30 сек — возвращаем пустой ответ
  const timer = setTimeout(() => {
    pending.delete(userId);
    res.json({ type: 'timeout' });
  }, 30_000);

  // Очистка при закрытии соединения клиентом
  req.on('close', () => {
    clearTimeout(timer);
    pending.delete(userId);
  });
});

// Когда появилось уведомление — отвечаем ожидающему клиенту
function pushNotification(userId, notification) {
  const res = pending.get(userId);
  if (res) {
    pending.delete(userId);
    res.json({ type: 'notification', data: notification });
  }
}

Частые ошибки

  • Не ставят таймаут на сервере — соединения «зависают» при уходе клиента
  • Не обрабатывают событие close запроса — утечка памяти (res остаётся в Map)
  • Не делают паузу при ошибке на клиенте — бесконечный цикл запросов при недоступном сервере
  • Используют long polling для высокочастотных событий — лучше WebSocket или SSE

Связанные темы

Ресурсы