Что такое Server-Sent Events и зачем они вам нужны?
Представьте: вы открыли сайт с курсами валют, и курс доллара меняется прямо на глазах — без обновления страницы, без нажатия кнопок. Или вы ждёте результат обработки большого файла на сервере, и вдруг появляется строка: «Готово на 70%». Как это работает? Один из самых элегантных способов — Server-Sent Events (SSE).
SSE — это технология, которая позволяет серверу самостоятельно отправлять данные клиенту в любой момент, как только они появляются. В отличие от WebSocket, SSE работает только в одну сторону — от сервера к клиенту. Но в этом и его суперсила: он невероятно прост, эффективен и идеально подходит для задач, где клиенту нужно лишь получать обновления.
В этой статье мы разберёмся, как использовать Server-Sent Events в JavaScript, напишем рабочий код и поймём, когда SSE — лучший выбор, а когда лучше посмотреть в сторону WebSocket. Готовы? Поехали!
Как работают Server-Sent Events (SSE)?
Технически SSE — это просто HTTP-соединение, которое сервер не закрывает после отправки ответа. Вместо этого он шлёт данные частями, используя специальный формат text/event-stream. Клиент (браузер) слушает это соединение через объект EventSource и генерирует события при получении каждого нового сообщения.
Вот как выглядит типичный ответ сервера:
data: {"message": "Привет!", "timestamp": 1700000000}
data: {"message": "Новое уведомление", "timestamp": 1700000001}
Каждое сообщение начинается с data:, за которым следует пробел и само содержимое. Сообщения разделяются двумя символами новой строки \n\n. Сервер может также отправлять идентификаторы (id:) и типы событий (event:), но минимальный набор — это data.
Клиентская часть ещё проще:
const eventSource = new EventSource('/events');
eventSource.onmessage = (e) => {
console.log('Получены данные:', e.data);
};
eventSource.onerror = (e) => {
console.error('Ошибка соединения', e);
};
Всё! Никаких сложных рукопожатий, никаких библиотек — браузер сам переподключается при разрыве, сам обрабатывает потерю связи. Это и есть главная магия Server-Sent Events.
SSE vs WebSocket: когда что выбирать?
Часто новички думают: «Раз SSE проще, значит, он всегда лучше». Но это не так. У каждой технологии своя ниша. Давайте сравним.
| Характеристика | SSE | WebSocket |
|---|---|---|
| Направление данных | Только сервер → клиент | Двустороннее |
| Простота реализации | Очень простая (нативный браузер) | Средняя (нужна библиотека) |
| Автоматическое переподключение | Встроенное | Нужно реализовывать вручную |
| Поддержка браузерами | Все современные (кроме IE) | Все современные |
| Протокол | HTTP (проходит через прокси) | ws:// (может блокироваться) |
Когда использовать SSE?
- Лента новостей, курс валют, котировки акций
- Push-уведомления от сервера
- Мониторинг выполнения задач (прогресс-бары)
- Логирование в реальном времени
Когда нужен WebSocket?
- Онлайн-игры, чаты, редакторы кода (нужна двусторонняя связь)
- Приложения, где клиент активно отправляет данные
Если ваша задача — получать обновления от сервера, и вам не нужно ничего отправлять обратно, Server-Sent Events — идеальный выбор. Он проще, легче и не требует дополнительных библиотек.
Пишем клиент на JavaScript с EventSource
Давайте создадим полноценный пример. Предположим, у нас есть сервер, который каждую секунду присылает случайное число. Напишем клиент, который будет отображать эти числа и считать статистику.
// client.js
const url = 'http://localhost:3000/stream';
const eventSource = new EventSource(url);
// Обработчик общих сообщений
eventSource.onmessage = (event) => {
const data = JSON.parse(event.data);
const container = document.getElementById('numbers');
const newItem = document.createElement('li');
newItem.textContent = `Получено: ${data.value} в ${new Date(data.timestamp * 1000).toLocaleTimeString()}`;
container.appendChild(newItem);
};
// Обработчик специального события "close"
eventSource.addEventListener('close', () => {
console.log('Сервер завершил поток');
eventSource.close();
});
// Обработчик ошибок
eventSource.onerror = (err) => {
console.error('Ошибка SSE:', err);
// Браузер автоматически попробует переподключиться через 3 секунды
};
// Кнопка для принудительного закрытия
document.getElementById('stop').addEventListener('click', () => {
eventSource.close();
console.log('Соединение закрыто вручную');
});
Обратите внимание на метод addEventListener. С помощью него можно ловить именованные события, которые сервер отправляет с полем event:. Например:
event: close
data: {"reason": "Задача завершена"}
Также полезно знать, что EventSource автоматически передаёт в заголовке запроса Last-Event-ID, если сервер отправлял id:. Это позволяет восстановить поток с последнего полученного сообщения после