🧱 Суть
AbortController позволяет отменять выполнение fetch-запросов.
В React он часто используется внутри useEffect, чтобы старые асинхронные операции не влияли на новые данные.
Основная проблема, которую он решает:
старый запрос не должен перезаписывать результат нового запроса
📦 Проблема
1. Гонки запросов (race condition)
Например, пользователь вводит:
Е
Ек
Ека
ЕкатКаждый символ запускает новый запрос:
request 1 → Е
request 2 → Ек
request 3 → Ека
request 4 → ЕкатНо ответы могут прийти не в том порядке:
request 4 → Екат
request 1 → ЕСтарый ответ может затереть актуальные данные.
2. fetch продолжает работать после изменения данных
Без отмены:
- пользователь меняет ввод
- старый запрос всё ещё выполняется
- ответ приходит позже
setSuggestions()записывает устаревшие данные
✅ Решение
Использовать AbortController внутри useEffect.
При каждом новом запуске эффекта:
- создаётся новый контроллер
- старый запрос отменяется через cleanup
- запускается новый запрос с актуальными данными
💻 Код
Создание контроллера:
const controller = new AbortController();
const { signal } = controller;Передача в fetch:
fetch(url, {
signal
});Отмена предыдущего запроса:
return () => {
controller.abort();
};Обработка отмены:
catch (error) {
if (error.name === 'AbortError') {
console.log('Запрос отменён');
}
}Как работает useEffect cleanup
При изменении зависимостей:
[inputValue, citySelected]React сначала вызывает старый return:
return () => controller.abort();И только потом запускает новый эффект.
Получается:
старый effect
↓
abort()
↓
новый effect
↓
новый fetchВажный нюанс
AbortController не отменяет уже отправленный HTTP-запрос на уровне сети.
То есть:
браузер → серверЗапрос может уже уйти.
Но после:
controller.abort();результат больше не будет обработан.
Итог:
- запрос мог отправиться ✅
- данные не попадут в состояние React ✅
Debounce + AbortController
AbortController решает проблему устаревших ответов.
Но он не уменьшает количество запросов.
Например:
Е → запрос
Ек → запрос
Ека → запрос
Екат → запросВсе они могут уйти.
Для уменьшения количества запросов используют debounce.
Пример debounce
const timeout = setTimeout(() => {
fetchCitySuggestion(inputValue);
}, 500);
return () => {
clearTimeout(timeout);
controller.abort();
};Теперь:
- пользователь печатает → таймер сбрасывается
- остановился на 500 мс → отправляется запрос
- старые запросы отменяются
🎯 Вывод
AbortControllerзащищает от устаревших ответовuseEffect returnиспользуется как cleanup перед следующим запуском- это предотвращает race condition
AbortControllerне уменьшает количество запросов- для этого нужен
debounce - лучший вариант для поиска:
debounce + AbortController