Перейти к основному содержимому

Отмена запросов и поток событий с сервера

Разработчику Архитектору

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 можно передать в несколько fetchabort() прервёт все.


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-поиск и поток статуса

Частый рабочий кейс сочетает обе темы статьи:

  1. Поле поиска отправляет fetch на каждый ввод с debounce.
  2. Перед новым запросом предыдущий прерывается через AbortController.
  3. После выбора результата открывается карточка задачи.
  4. Карточка подписывается на SSE-канал статуса (in-progress, ready, failed).

Такая схема убирает гонки ответов в поиске и даёт живые обновления без ручного опроса каждые 2-5 секунд. Для тяжёлых интеграций с долгими задачами она обычно экономит трафик и упрощает клиентскую логику.


Чек-лист внедрения в проект

  • У каждого запроса есть контроллер и единая обработка AbortError.
  • Для SSE добавлен heartbeat на сервере и source.close() при pagehide.
  • Критичные события в SSE имеют id, чтобы клиент мог восстановиться после обрыва.
  • В логах отделены "отмена пользователем" и "сетевая ошибка".
  • Точки входа связаны с материалами про BOM и жизненный цикл вкладки и наблюдатели DOM, если поток влияет на интерфейс.

Краткий итог

AbortController — стандартный способ отменить fetch и избежать гонок при быстром вводе. EventSource — простой поток событий с сервера без полноценного WebSocket. Оба дополняют главу про асинхронность, а не заменяют её.


Основа по протоколу

Базовый разбор HTTP и HTTPS находится в отдельной статье — HTTP как основа веб-интеграций.