Fetch: прерывание запроса
Как отменять fetch-запросы в JavaScript с помощью AbortController и AbortSignal: отмена по действию пользователя, тайм-ауты, массовая отмена запросов и правильная обработка AbortError.
Как только запрос fetch() отправлен, он продолжает выполняться до получения ответа от сервера — даже если пользователь уже перешёл на другую страницу, ввёл новый поисковый запрос или результат больше не нужен. Такие лишние запросы занимают соединения, расходуют заряд батареи и трафик и могут вернуть устаревшие данные, перезаписав свежие. Интерфейс AbortController предоставляет стандартный и чистый способ отменить запрос по требованию.
В этой главе показано, как подключить AbortController, реагировать на действия пользователя, реализовывать тайм-ауты, отменять несколько запросов одновременно и корректно обрабатывать возникающий AbortError. Подробнее о самом fetch читайте на предыдущей странице — Fetch API.
Как работает AbortController
AbortController — небольшой объект с единственной задачей: он владеет AbortSignal и может перевести этот сигнал в состояние отменён (aborted). Именно сигнал вы передаёте в fetch() (а также во многие другие API браузера, например в addEventListener). Когда вы вызываете controller.abort(), все операции, получившие этот сигнал, отменяются.
Схема всегда состоит из трёх одинаковых шагов:
- Создайте контроллер:
const controller = new AbortController(). - Передайте
controller.signalвfetch()в объекте настроек. - Вызовите
controller.abort()в тот момент, когда нужно отменить запрос.
При отмене fetch его промис отклоняется с DOMException, у которого свойство name равно "AbortError". Именно поэтому в каждом примере ниже проверяется error.name === 'AbortError' — чтобы игнорировать намеренную отмену, не подавляя при этом реальные сетевые ошибки.
Базовое использование AbortController
Вот минимальный законченный пример. Запрос отменяется немедленно, чтобы можно было увидеть путь отклонения:
Мы создаём AbortController, передаём его signal в fetch() и сразу же вызываем controller.abort(). Поскольку запрос не завершается в штатном режиме, срабатывает .catch(), который сообщает об отмене.
Проверка сигнала: aborted и событие abort
Сигнал открывает доступ к своему состоянию, чтобы другой код мог реагировать на отмену. Наиболее важны два члена:
signal.aborted— boolean, который становитсяtrueпосле отмены.- событие
"abort"— генерируется на сигнале в момент вызоваabort().
Это удобно, когда у вас есть не связанная с 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() отменяет их все — это удобно, когда страница покидает представление, запустившее несколько параллельных загрузок:
Поскольку все три запроса используют один сигнал, controller.abort() отменяет их одним вызовом, а Promise.all отклоняется с AbortError. Подробнее о параллельном выполнении запросов читайте в Promise API.
Отмена по тайм-ауту
Очень распространённый способ использования AbortController — установить дедлайн для запроса: если сервер отвечает слишком долго, отменить запрос и показать ошибку вместо бесконечного ожидания.
Ручной способ с setTimeout
Контроллер можно совместить с таймером и любой другой асинхронной логикой. Здесь отдельная операция инициирует отмену через одну секунду, тогда как эндпоинт отвечал бы за пять:
Запрос отменяется таймером через одну секунду — задолго до того, как пятисекундный эндпоинт успевает ответить. Подробнее о таймерах читайте в разделе async/await.
Короткий способ: AbortSignal.timeout()
Современные браузеры (и Node 17.3+) поставляются со встроенным вспомогательным методом, который создаёт сигнал, автоматически отменяющийся через заданное количество миллисекунд — без контроллера и setTimeout:
Обратите внимание: отмена по тайм-ауту отклоняет промис с 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. Применение этих паттернов делает приложения отзывчивыми и избавляет их от бесполезного ожидания ненужных результатов.