🧱 Суть

AbortController позволяет отменять выполнение fetch-запросов.

В React он часто используется внутри useEffect, чтобы старые асинхронные операции не влияли на новые данные.

Основная проблема, которую он решает:

старый запрос не должен перезаписывать результат нового запроса

📦 Проблема

1. Гонки запросов (race condition)

Например, пользователь вводит:

Е
Ек
Ека
Екат

Каждый символ запускает новый запрос:

request 1 → Е
request 2 → Ек
request 3 → Ека
request 4 → Екат

Но ответы могут прийти не в том порядке:

request 4 → Екат
request 1 → Е

Старый ответ может затереть актуальные данные.

2. fetch продолжает работать после изменения данных

Без отмены:

  • пользователь меняет ввод
  • старый запрос всё ещё выполняется
  • ответ приходит позже
  • setSuggestions() записывает устаревшие данные

✅ Решение

Использовать AbortController внутри useEffect.

При каждом новом запуске эффекта:

  1. создаётся новый контроллер
  2. старый запрос отменяется через cleanup
  3. запускается новый запрос с актуальными данными

💻 Код

Создание контроллера:

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

🔗 Связано