Abort Controller: отмена запросов
AbortController — браузерный API для отмены асинхронных операций: fetch-запросов, потоков чтения и других задач, поддерживающих AbortSignal.
Зачем нужно
Без отмены запросов быстрые действия пользователя (смена маршрута, повторный поиск) порождают «гонку ответов»: старый запрос может прийти позже нового и перезаписать актуальные данные. AbortController позволяет отменить устаревший запрос до получения ответа, что снижает нагрузку на сеть и устраняет баги состояния гонки.
Где используется
- Живой поиск (debounce + abort предыдущего запроса при новом вводе)
- React/Vue компоненты — отмена запроса при размонтировании компонента
- Пагинация — отмена предыдущей страницы при переходе на следующую
- Таймаут запроса без нативной поддержки в fetch
Основы AbortController
// Создаём контроллер и получаем его сигнал
const controller = new AbortController();
const { signal } = controller;
// Передаём сигнал в fetch
fetch('/api/search?q=query', { signal })
.then(res => res.json())
.then(data => console.log(data))
.catch(err => {
if (err.name === 'AbortError') {
console.log('Запрос отменён'); // ожидаемая ситуация
} else {
console.error('Сетевая ошибка:', err);
}
});
// Отменяем запрос
controller.abort();
// AbortError брошен — промис fetch отклонён
Живой поиск с отменой
let controller = null;
async function search(query) {
// Отменяем предыдущий запрос, если он ещё не завершился
if (controller) {
controller.abort();
}
controller = new AbortController();
try {
const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`, {
signal: controller.signal,
});
const data = await res.json();
renderResults(data);
} catch (err) {
if (err.name !== 'AbortError') throw err;
}
}
document.querySelector('#search').addEventListener('input', e => {
search(e.target.value);
});
Отмена в React useEffect
function SearchResults({ query }) {
const [results, setResults] = React.useState();
React.useEffect(() => {
const controller = new AbortController();
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(res => res.json())
.then(data => setResults(data))
.catch(err => {
if (err.name !== 'AbortError') console.error(err);
});
// cleanup — отменяем при смене query или размонтировании
return => controller.abort();
}, [query]);
return <ul>{results.map(r => <li key={r.id}>{r.name}</li>)}</ul>;
}
Таймаут через AbortController
async function fetchWithTimeout(url, ms = 5000) {
const controller = new AbortController();
const timerId = setTimeout( => controller.abort(), ms);
try {
const res = await fetch(url, { signal: controller.signal });
return await res.json();
} finally {
clearTimeout(timerId);
}
}
// Начиная с Node 18 / Chrome 124 есть AbortSignal.timeout:
const res = await fetch(url, { signal: AbortSignal.timeout(5000) });
Частые ошибки
- Не проверяют
err.name === 'AbortError'— обрабатывают отмену как ошибку сети - Создают новый
AbortControllerвнутри обработчика события без отмены предыдущего - Забывают вернуть cleanup-функцию из
useEffect— запрос продолжает работать после размонтирования - Вызывают
controller.abort()после получения ответа — бесполезно, сигнал уже не нужен
Связанные темы
- _MOC Сеть
- _MOC JavaScript
- Fetch -- headers, mode, credentials
- Retry с экспоненциальным backoff
- REST API