Дата публикации:
В современных веб‑приложениях взаимодействие клиента и сервера происходит через сетевые запросы. После того как сервер обработал запрос, необходимо доставить результат обратно в браузер (или другое клиентское приложение), где он будет обработан и отображён пользователю. В этой статье рассматриваются основные способы передачи данных от сервера к клиенту, их особенности, преимущества и типичные сценарии использования.
Основные подходы
- AJAX / Fetch API – асинхронный запрос из JavaScript, позволяющий обновлять части страницы без полной перезагрузки.
- WebSocket – постоянное duplex‑соединение, при котором сервер может инициировать отправку данных клиенту в реальном времени.
- Server‑Sent Events (SSE) – односторонний поток от сервера к клиенту, удобный для live‑лент, уведомлений и т.п.
- Long Polling – эмуляция push‑уведомлений через обычные HTTP‑запросы, используемая там, где WebSocket недоступен.
HTTP‑ответ (REST/GraphQL)
Самый простой способ – клиент делает запрос GET, POST, PUT и т.д., а сервер формирует ответ с нужным статусом и телом.
Пример на Node.js (Express)
const express = require('express');
const app = express;
app.get('/api/users/:id', (req, res) => {
const user = { id: req.params.id, name: 'Иван Иванов', age: 30 };
res.json(user); // автоматически ставит Content-Type: application/json
});
app.listen(3000, => console.log('Server running on port 3000'));
Клиентская часть (Fetch)
fetch('/api/users/42')
.then(response => response.json)
.then(data => {
document.getElementById('userInfo').textContent =
`Имя: ${data.name}, Возраст: ${data.age}`;
})
.catch(err => console.error('Ошибка:', err));
Преимущества:
- Простота реализации и отладки.
- Широкая поддержка браузерами и серверными фреймворками.
- Кешируемость (при правильных заголовках
Cache-Control,ETag).
Недостатки:
- Запрос‑ответ модели подразумевает задержку, равную RTT (round‑trip time).
- Для частых обновлений требуется множество запросов.
AJAX / Fetch API
AJAX (Asynchronous JavaScript and XML) – техника, позволяющая выполнять запросы на сервер без перезагрузки страницы. Современный стандарт – fetch.
Пример с POST‑запросом
async function createComment(text) {
const response = await fetch('/api/comments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json;
return result; // объект, полученный от сервера
}
// Использование
createComment('Привет, мир!')
.then(comment => {
const li = document.createElement('li');
li.textContent = comment.text;
document.getElementById('commentsList').appendChild(li);
}) .catch(console.error);
Преимущества:
- Полный контроль над заголовками, методом и телом запроса.
- Возможность обрабатывать ответы постепенно (например, через
ReadableStream).
Недостатки:
- Все ещё требует явного инициирования запроса со стороны клиента.
- Нужно самостоятельно обрабатывать ошибки и таймауты.
WebSocket
WebSocket предоставляет постоянное двустороннее соединение через один TCP‑порт. После установления handshake клиент и сервер могут посылать друг другу сообщения в любое время.
Сервер (Node.js + ws)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', ws => {
console.log('Клиент подключен');
// При получении сообщения от клиента
ws.on('message', message => {
const data = JSON.parse(message);
// Обработка... предположим, что нужно вернуть результат вычисления
const result = { expr: data.expr, value: eval(data.expr) };
ws.send(JSON.stringify(result));
});
ws.on('close', => console.log('Клиент отключен'));
});
Клиент
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('open', => {
socket.send(JSON.stringify({ expr: '2 + 2 * 3' }));
});
socket.addEventListener('message', event => {
const data = JSON.parse(event.data);
document.getElementById('result').textContent =
`${data.expr} = ${data.value}`;
});
socket.addEventListener('error', err => console.error('WebSocket error:', err));
Преимущества:
- Минимальная задержка после установления соединения.
- Сервер может инициировать отправку данных (push).
- Эффективно для высокочастотного обмена (игры, чаты, биржевые котировки).
Недостатки:
- Более сложная обработка соединений (переподключения, heartbeat).
- Требует поддержки со стороны сервера и иногда прокси/балансировщиков.
- Не подходит для простых запросов‑ответов, где достаточно REST.
Server‑Sent Events (SSE)
SSE – односторонний поток от сервера к клиенту, основанный на HTTP. Клиент открывает соединение EventSource и получает события вида data: {...}
.
Сервер (Express)
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders;
const interval = setInterval( => {
const data = { timestamp: new Date.toISOString, value: Math.random };
res.write(`data: ${JSON.stringify(data)}
`);
}, 1000);
req.on('close', => {
clearInterval(interval);
res.end;
});
});
const evtSource = new EventSource('/events');
evtSource.onmessage = event => {
const data = JSON.parse(event.data); `[${data.timestamp}] ${data.value.toFixed(3)}
`;
};
evtSource.onerror = err => {
console.error('SSE error:', err);
evtSource.close;
};
Преимущества:
- Автоматическое восстановление соединения после обрыва.
- Проще, чем WebSocket, если нужна только сервер‑→‑клиент передача.
- Работает через обычный HTTP, поэтому легко проходит через прокси.
Недостатки:
- Односторонний – клиент не может посылать данные серверу через тот же канал (нужен отдельный запрос).
- Не поддерживается в IE/Edge старых версий.
Long Polling
Long Polling – техника, при которой клиент отправляет запрос, а сервер держит его открытым до появления новых данных или истечения таймаута; После получения ответа клиент немедленно отправляет новый запрос.
Пример серверной логики (pseudo‑code)
function handleLongPoll(req, res) {
const timeout = setTimeout( => {
res.json({ message: 'timeout' });
}, 30000); // 30 сек
// Предположим, есть очередь сообщений для пользователя
userMessageQueue.on('message', msg => {
clearTimeout(timeout);
res.json({ message: msg });
});
req.on('close', => {
clearTimeout(timeout);
userMessageQueue.removeListener('message', /* handler */);
});
}
Клиент (Fetch с рекурсией)
function startLongPoll {
fetch('/longpoll')
.then(r => r.json)
.then(data => {
console.log('Получено:', data);
// обработка данных
startLongPoll; // сразу запускаем следующий запрос
}) .catch(err => {
console.error('Ошибка long polling:', err);
setTimeout(startLongPoll, 5000); // повтор через 5 сек после ошибки
});
}
startLongPoll;
Преимущества:
- Работает даже там, где WebSocket и SSE блокируются.
- Прост в реализации на стороне сервера (можно использовать обычные обработчики).
Недостатки:
- Больше нагрузки на сервер из‑за множества открытых соединений.
- Задержка зависит от интервала между запросами.
- Сложнее гарантировать порядок доставки при падении соединения.
Выбор подхода
| Критерий | REST/AJAX | WebSocket | SSE | Long Polling |
|---|---|---|---|---|
| Направление | Запрос‑ответ | Дуплекс | Сервер→Клиент | Запрос‑ответ (с удержанием) |
| Задержка | Средняя (RTT) | Низкая | Низкая | Средняя‑высокая |
| Сложность | Низкая | Средняя‑высокая | Низкая | Средняя |
| Поддержка браузеров | Все | Все современные | Все современные (IE нет) | Все |
| Когда использовать | Обычные CRUD‑операции, редкие обновления | Чат, онлайн‑игры, торги, совместное редактирование | Live‑ленты, уведомления, мониторинг | Среды без WebSocket/SSE, необходимость простого fallback |
Возврат результата запроса с сервера на клиент – фундаментальная часть любой веб‑системы. Выбор конкретного механизма зависит от требований к задержке, объёму данных, направленности потока и ограничений инфраструктуры. Для большинства типичных приложений достаточно REST/JSON в сочетании с Fetch API. Когда нужна реальная времени двусторонняя связь – WebSocket становится оптимальным решением. Если достаточно односторонних обновлений от сервера, стоит рассмотреть SSE, а в случае ограничений – Long Polling.
Понимание особенностей каждого подхода позволяет построить надёжное, масштабируемое и удобное для пользователей приложение.
Статья подготовлена . Все примеры кода носят illustrative характер и могут требовать адаптации под конкретный стек технологий.