W3docs

Fetch: прерывание запроса

Как отменять fetch-запросы в JavaScript с помощью AbortController и AbortSignal: отмена по действию пользователя, тайм-ауты, массовая отмена запросов и правильная обработка AbortError.

Как только запрос fetch() отправлен, он продолжает выполняться до получения ответа от сервера — даже если пользователь уже перешёл на другую страницу, ввёл новый поисковый запрос или результат больше не нужен. Такие лишние запросы занимают соединения, расходуют заряд батареи и трафик и могут вернуть устаревшие данные, перезаписав свежие. Интерфейс AbortController предоставляет стандартный и чистый способ отменить запрос по требованию.

В этой главе показано, как подключить AbortController, реагировать на действия пользователя, реализовывать тайм-ауты, отменять несколько запросов одновременно и корректно обрабатывать возникающий AbortError. Подробнее о самом fetch читайте на предыдущей странице — Fetch API.

Как работает AbortController

AbortController — небольшой объект с единственной задачей: он владеет AbortSignal и может перевести этот сигнал в состояние отменён (aborted). Именно сигнал вы передаёте в fetch() (а также во многие другие API браузера, например в addEventListener). Когда вы вызываете controller.abort(), все операции, получившие этот сигнал, отменяются.

Схема всегда состоит из трёх одинаковых шагов:

  1. Создайте контроллер: const controller = new AbortController().
  2. Передайте controller.signal в fetch() в объекте настроек.
  3. Вызовите controller.abort() в тот момент, когда нужно отменить запрос.

При отмене fetch его промис отклоняется с DOMException, у которого свойство name равно "AbortError". Именно поэтому в каждом примере ниже проверяется error.name === 'AbortError' — чтобы игнорировать намеренную отмену, не подавляя при этом реальные сетевые ошибки.

Базовое использование AbortController

Вот минимальный законченный пример. Запрос отменяется немедленно, чтобы можно было увидеть путь отклонения:

javascript— editable

Мы создаём AbortController, передаём его signal в fetch() и сразу же вызываем controller.abort(). Поскольку запрос не завершается в штатном режиме, срабатывает .catch(), который сообщает об отмене.

Проверка сигнала: aborted и событие abort

Сигнал открывает доступ к своему состоянию, чтобы другой код мог реагировать на отмену. Наиболее важны два члена:

  • signal.aborted — boolean, который становится true после отмены.
  • событие "abort" — генерируется на сигнале в момент вызова abort().
javascript— editable

Это удобно, когда у вас есть не связанная с fetch работа (таймер, анимация, чтение потока), которую нужно остановить в момент отмены запроса.

Один контроллер — одно использование. Контроллер нельзя сбросить. После вызова abort() сигнал остаётся в состоянии «отменён» навсегда, и любой новый fetch(), запущенный с этим сигналом, немедленно отклоняется. Для нового запроса создавайте новый AbortController.

Практический пример: отмена по действию пользователя

Наиболее частая причина отмены — пользователь передумал: нажал «Отмена», закрыл диалог или ввёл новый запрос до того, как завершился предыдущий. Здесь кнопка отменяет выполняющийся запрос:

<body>
  <button id="abortButton">Abort Fetch Request</button>
  <script>
    const controller = new AbortController();
    const signal = controller.signal;
    document.getElementById('abortButton').addEventListener('click', () => {
      controller.abort();
    });
    fetch('https://httpbin.org/delay/5', { signal })
      .then(response => response.json())
      .then(data => alert(
        'Data is successfully fetched! Refresh the page and try aborting.'
      ))
      .catch(error => {
        if (error.name === 'AbortError') {
          alert('Fetch request was aborted by the user');
        } else {
          alert('Fetch error: ' + error.message);
        }
      });
  </script>
</body>

Нажатие кнопки с идентификатором abortButton отменяет выполняющийся fetch-запрос. Эндпоинт https://httpbin.org/delay/5 намеренно отвечает через 5 секунд, поэтому если нажать кнопку в этот промежуток, запрос отклонится с AbortError.

Отмена нескольких запросов одновременно

Один сигнал можно передать множеству запросов. Один вызов controller.abort() отменяет их все — это удобно, когда страница покидает представление, запустившее несколько параллельных загрузок:

javascript— editable

Поскольку все три запроса используют один сигнал, controller.abort() отменяет их одним вызовом, а Promise.all отклоняется с AbortError. Подробнее о параллельном выполнении запросов читайте в Promise API.

Отмена по тайм-ауту

Очень распространённый способ использования AbortController — установить дедлайн для запроса: если сервер отвечает слишком долго, отменить запрос и показать ошибку вместо бесконечного ожидания.

Ручной способ с setTimeout

Контроллер можно совместить с таймером и любой другой асинхронной логикой. Здесь отдельная операция инициирует отмену через одну секунду, тогда как эндпоинт отвечал бы за пять:

javascript— editable

Запрос отменяется таймером через одну секунду — задолго до того, как пятисекундный эндпоинт успевает ответить. Подробнее о таймерах читайте в разделе async/await.

Короткий способ: AbortSignal.timeout()

Современные браузеры (и Node 17.3+) поставляются со встроенным вспомогательным методом, который создаёт сигнал, автоматически отменяющийся через заданное количество миллисекунд — без контроллера и setTimeout:

javascript— editable

Обратите внимание: отмена по тайм-ауту отклоняет промис с TimeoutError, а не AbortError, что позволяет различить «запрос занял слишком много времени» и «пользователь отменил». Если нужны оба варианта — тайм-аут и кнопка ручной отмены, — объедините сигналы с помощью AbortSignal.any([userSignal, AbortSignal.timeout(2000)]).

Правильная обработка AbortError

Каждый раз, когда вы отменяете fetch, его промис отклоняется. Если не обработать это, в консоли появится шумное сообщение «uncaught promise rejection», хотя отмена была намеренной. Два правила помогут сохранить чистоту:

  • Всегда добавляйте .catch() (или try/catch с await) к отменяемому fetch-запросу.
  • Внутри обработчика проверяйте error.name и воспринимайте 'AbortError' / 'TimeoutError' как ожидаемое поведение — логируйте или показывайте только остальные ошибки. Общую схему см. в разделе Обработка ошибок с промисами.

Заключение

AbortController — стандартный способ отменять fetch()-запросы в JavaScript. Создайте контроллер, передайте его signal в один или несколько запросов и вызовите abort(), когда работа больше не нужна. В этой главе рассмотрены отмена по действию пользователя, ручные и встроенные тайм-ауты, отмена нескольких запросов одновременно и обработка возникающего AbortError. Применение этих паттернов делает приложения отзывчивыми и избавляет их от бесполезного ожидания ненужных результатов.

Практика

Практика
Для чего нужно передавать свойство signal из AbortController в fetch-запрос?
Для чего нужно передавать свойство signal из AbortController в fetch-запрос?
Was this page helpful?