Отмена запросов и поток событий с сервера
AbortController - отмена fetch-запросов
Пользователь ушёл со страницы, ввёл новый поиск, закрыл модальное окно — старый fetch всё ещё ждёт ответ. Без отмены:
- тратится сеть и память;
- устаревший ответ может перезаписать актуальные данные на экране.
Базовый fetch разобран в асинхронном программировании. Галерея GET, POST, таймаута и React useEffect с разбором — Fetch / axios — типовые запросы; готовый компонент списка с API — React — компоненты-рецепты. Здесь — прерывание и односторонний поток с сервера.
Гонка ответов при вводе в поиске
Пользователь печатает java, затем быстро заменяет на javascript. Сервер отвечает на оба запроса, но не обязательно в порядке ввода — без отмены на экране окажутся устаревшие результаты. AbortController отменяет предыдущий fetch при старте нового; для потоковых обновлений с сервера — EventSource (ниже).
AbortController и fetch
AbortController выдаёт сигнал signal. Передайте его в fetch — при вызове abort() запрос прерывается, промис отклоняется с AbortError.
Код ITЗагрузка примера кода…
Разбор:
new AbortController()создаёт объект управления отменой асинхронной операции.signalпередаётся вfetch, чтобы связать конкретный запрос с этим контроллером.- В
catchпроверяетсяerror.name === 'AbortError', чтобы отличить ожидаемую отмену от реальной ошибки сети или сервера. - Вызов
controller.abort()мгновенно завершает ожидание старого ответа и предотвращает гонки данных в UI. - Такая схема особенно полезна для поиска "по мере ввода", где запросы часто устаревают через доли секунды.
Таймаут
function fetchWithTimeout(url, options = {}, ms = 8000) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), ms);
return fetch(url, { ...options, signal: controller.signal }).finally(() => {
clearTimeout(id);
});
}
Разбор:
- Функция объединяет
fetchи таймаут в одном reusable-хелпере. setTimeout(() => controller.abort(), ms)запускает автоматическую отмену, если ответ задерживается дольше заданного лимита.- Оператор расширения
{ ...options, signal: controller.signal }сохраняет входные параметры и дополняет их сигналом отмены. - Блок
finallyвсегда выполняется и очищает таймер черезclearTimeout(id), чтобы не оставлять лишние задачи в event loop. - Подход улучшает устойчивость интерфейса при нестабильной сети и долгих API-операциях.
Один контроллер на компонент
При размонтировании виджета (виджеты на ванильном JS) отменяйте все висящие запросы:
Код ITЗагрузка примера кода…
Разбор:
this.controller?.abort()отменяет предыдущий запрос перед запуском нового и защищает от устаревших ответов.this.controller = new AbortController()создаёт свежий канал отмены для текущего поиска.encodeURIComponent(this.query)корректно кодирует пользовательский текст в URL-параметре.- Обработчик ошибок повторно пробрасывает только не-
AbortError, чтобы не скрывать реальные проблемы. - Метод
destroy()завершает активный запрос при удалении виджета и снижает риск утечек.
Несколько операций
Один signal можно передать в несколько fetch — abort() прервёт все.
EventSource (Server-Sent Events)
SSE — долгоживущее HTTP-соединение, по которому сервер шлёт текстовые события клиенту. Клиент только слушает (в отличие от WebSocket, где канал двусторонний).
Подход относится к семейству Comet — веб-моделей, где сервер инициирует доставку данных без очередного полного запроса страницы. Раньше для этого чаще использовали long polling на XHR; EventSource даёт стандартный односторонний поток поверх обычного HTTP. Сравнение с AJAX и XHR — в главе асинхронное программирование.
Код ITЗагрузка примера кода…
Разбор:
new EventSource(...)открывает долгоживущее соединение для односторонних серверных событий.- Событие
openсообщает об успешном установлении канала и полезно для статуса подключения. - В обработчике
messageстрокаevent.dataобычно парсится из JSON в объект для рендера. - Кастомные события (
ping) позволяют разделять типы сообщений на уровне протокола SSE. source.close()обязательно закрывает поток при уходе со страницы или уничтожении компонента.
На сервере ответ с заголовками Content-Type — text/event-stream, Cache-Control: no-cache, тело в формате:
event: message
data: {"id":1,"text":"Новый заказ"}
Разбор:
- Строка
event: messageзадаёт тип события, на который подписывается клиентский обработчик. - Поле
data:содержит полезную нагрузку, чаще всего в JSON-формате. - Пустая строка в конце отделяет одно событие от следующего в SSE-потоке.
- Такой текстовый формат прост для генерации на сервере и хорошо проксируется через HTTP-инфраструктуру.
| Критерий | SSE (EventSource) | WebSocket |
|---|---|---|
| Направление | сервер → клиент | оба направления |
| Протокол | обычный HTTP | отдельный ws/wss |
| Прокси / CDN | проще | иногда нужны настройки |
| Типичное применение | уведомления, лента, прогресс | чат, игра, совместное редактирование |
Для отправки данных на сервер при SSE используйте обычный fetch POST параллельно с потоком.
Ошибки и CORS
AbortError— не показывать как "сеть недоступна".- SSE с другого origin требует CORS и корректных заголовков; cookies —
withCredentials: trueуEventSource, если нужна сессия. - При закрытии вкладки вызывайте
source.close(), иначе соединение висит до таймаута.
Практический сценарий — live-поиск и поток статуса
Частый рабочий кейс сочетает обе темы статьи:
- Поле поиска отправляет
fetchна каждый ввод с debounce. - Перед новым запросом предыдущий прерывается через
AbortController. - После выбора результата открывается карточка задачи.
- Карточка подписывается на SSE-канал статуса (
in-progress,ready,failed).
Такая схема убирает гонки ответов в поиске и даёт живые обновления без ручного опроса каждые 2-5 секунд. Для тяжёлых интеграций с долгими задачами она обычно экономит трафик и упрощает клиентскую логику.
Чек-лист внедрения в проект
- У каждого запроса есть контроллер и единая обработка
AbortError. - Для SSE добавлен heartbeat на сервере и
source.close()приpagehide. - Критичные события в SSE имеют
id, чтобы клиент мог восстановиться после обрыва. - В логах отделены "отмена пользователем" и "сетевая ошибка".
- Точки входа связаны с материалами про BOM и жизненный цикл вкладки и наблюдатели DOM, если поток влияет на интерфейс.
Краткий итог
AbortController — стандартный способ отменить fetch и избежать гонок при быстром вводе. EventSource — простой поток событий с сервера без полноценного WebSocket. Оба дополняют главу про асинхронность, а не заменяют её.
Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.