Когда «живой» сайт требует живого кода
Представьте: вы пишете мессенджер на TypeScript. Пользователь отправляет сообщение — и собеседник должен увидеть его мгновенно, без обновления страницы. Или биржевой дашборд, где цена акций меняется каждую секунду. Или просто лента уведомлений, которая обновляется сама. Как это реализовать? Два главных кандидата — long polling и WebSocket. Но какой выбрать, чтобы не провалить производительность и не сломать UX?
В этой статье мы на реальных примерах на TypeScript разберем, как работает каждая технология, где они блестят, а где подводят. Вы получите не только теорию, но и готовые куски кода для Node.js и браузера. Поехали!
Что такое Long Polling в TypeScript и как это работает
Long polling — это эволюция обычного HTTP-запроса. Клиент отправляет запрос на сервер, но сервер не отвечает сразу, если данных нет. Он держит соединение открытым до тех пор, пока не появится новое событие. Как только данные готовы — сервер отправляет ответ, клиент его обрабатывает и сразу же создает новый запрос. Цикл повторяется.
Звучит как костыль? Отчасти да. Но это работает даже через самые строгие корпоративные прокси, которые блокируют WebSocket.
Реализация Long Polling на TypeScript (серверная часть)
Напишем простой сервер на Express с TypeScript. Он будет эмулировать очередь сообщений.
import express, { Request, Response } from 'express';
import cors from 'cors';
const app = express();
app.use(cors());
app.use(express.json());
// Хранилище сообщений (имитация очереди)
const messages: string[] = [];
// Endpoint для long polling
app.get('/poll', (req: Request, res: Response) => {
// Ждем новое сообщение до 30 секунд
const checkForMessage = () => {
if (messages.length > 0) {
// Отправляем первое сообщение и удаляем его из очереди
const message = messages.shift();
res.json({ data: message, timestamp: Date.now() });
} else {
// Если сообщений нет — продолжаем ждать
setTimeout(checkForMessage, 1000); // проверяем каждую секунду
}
};
// Таймаут на случай, если сообщений долго нет
const timeout = setTimeout(() => {
res.json({ data: null, timeout: true });
}, 30000);
// Если клиент отключился — чистим таймеры
req.on('close', () => {
clearTimeout(timeout);
});
checkForMessage();
});
// Endpoint для отправки сообщения
app.post('/send', (req: Request, res: Response) => {
const { message } = req.body;
if (message) {
messages.push(message);
res.status(200).json({ status: 'ok' });
} else {
res.status(400).json({ error: 'message is required' });
}
});
app.listen(3000, () => console.log('Long poll server on port 3000'));
Клиентская часть на TypeScript (браузер)
async function startLongPolling() {
try {
const response = await fetch('http://localhost:3000/poll');
const data = await response.json();
if (data.data) {
console.log('Новое сообщение:', data.data);
// Обновляем UI
updateUI(data.data);
}
// Сразу запускаем новый poll
startLongPolling();
} catch (error) {
console.error('Ошибка long polling:', error);
// Ждем 2 секунды и пробуем снова
setTimeout(startLongPolling, 2000);
}
}
function updateUI(message: string) {
// Ваш код для обновления DOM
const container = document.getElementById('messages');
if (container) {
const item = document.createElement('li');
item.textContent = message;
container.appendChild(item);
}
}
// Стартуем при загрузке страницы
document.addEventListener('DOMContentLoaded', startLongPolling);
Вывод: long polling прост в реализации и не требует специальных протоколов. Но каждый запрос — это HTTP-заголовки, cookie, накладные расходы. При 1000 одновременных клиентов сервер держит 1000 открытых соединений.
WebSocket в TypeScript: постоянный канал связи
WebSocket — это полноценный двусторонний канал поверх TCP. После рукопожатия (через HTTP Upgrade) клиент и сервер могут обмениваться сообщениями в любой момент. Нет лишних заголовков, нет опроса — только данные.
В TypeScript WebSocket работает через стандартный WebSocket API в браузере и через библиотеки (например, ws) на сервере.
Сервер WebSocket на TypeScript (Node.js)
Установим библиотеку: npm install ws @types/ws
import { WebSocketServer, WebSocket } from 'ws';
const wss = new WebSocketServer({ port: 8080 });
// Хранилище всех подключений
const clients: Set<WebSocket> = new Set();
wss.on('connection', (ws: WebSocket) => {
console.log('Клиент подключился');
clients.add(ws);
// Обработка входящих сообщений от конкретного клиента
ws.on('message', (message: Buffer) => {
const text = message.toString();
console.log('Получено:', text);
// Рассылаем сообщение всем подключенным клиентам
clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(`[${new Date().toISOString()}] ${text}`);
}
});
});
ws.on('close', () => {
console.log('Клиент отключился');
clients.delete(ws);
});
ws.on('error', (err) => {
console.error('Ошибка WebSocket:', err);
clients.delete(ws);
});
});
console.log('WebSocket сервер запущен на ws://localhost:8080');
Клиент WebSocket на TypeScript (браузер)
class ChatClient {
private ws: WebSocket;
private url: string = 'ws://localhost:8080';
constructor() {
this.ws = new WebSocket(this.url);
this.init();
}
private init() {
this.ws.onopen = () => {
console.log('Соединение установлено');
this.sendMessage('Привет, сервер!');
};
this.ws.onmessage = (event: MessageEvent) => {
console.log('Сообщение от сервера:', event.data);
this.displayMessage(event.data);
};
this.ws.onerror = (error: Event) => {
console.error('Ошибка WebSocket:', error);
};
this.ws.onclose = () => {
console.log('Соединение закрыто');
// Автоматическое переподключение через 3 секунды
setTimeout(() => new ChatClient(), 3000);
};
}
public sendMessage(text: string) {
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(text);
} else {
console.warn('Соединение еще не открыто');
}
}
private displayMessage(msg: string) {
// Ваш код для UI
const container = document.getElementById('chat');
if (container) {
const div = document.createElement('div');
div.textContent = msg;
container.appendChild(div);
}
}
}
// Создаем экземпляр при загрузке
new ChatClient();
Обратите внимание: WebSocket требует, чтобы сервер тоже поддерживал этот протокол. На продакшене часто используют socket.io, который умеет fallback на long polling, если WebSocket не работает.
Long Polling vs WebSocket: таблица сравнения
Чтобы выбор был осознанным