AbortController / AbortSignal
AbortController — стандартный механизм отмены async-операций в браузере и Node.js 15+; правильное использование с fetch, таймаутами и cleanup предотвращает memory leaks, zombie-запросы и race conditions.
// Базовое использование с fetchconst controller = new AbortController();const { signal } = controller;
setTimeout(() => controller.abort('timeout reason'), 5000);
try { const res = await fetch('/api/data', { signal }); const data = await res.json(); return data;} catch (e) { if (e.name === 'AbortError') { console.log('Aborted, reason:', signal.reason); // 'timeout reason' } else { throw e; // пробрасываем сетевые и другие ошибки }}// AbortSignal.timeout() — статический helper (Node 17.3+, все браузеры)const res = await fetch('/api/data', { signal: AbortSignal.timeout(5000), // бросит TimeoutError при превышении});
// AbortSignal.any() — объединить несколько сигналов (Node 20+)const manualAbort = new AbortController();const combined = AbortSignal.any([ manualAbort.signal, AbortSignal.timeout(10_000),]);fetch('/api', { signal: combined });// Завершится при первом: manualAbort.abort() ИЛИ таймаут 10s// Правильный cleanup в React (без race condition)function useData(url) { const [data, setData] = useState(null); useEffect(() => { const controller = new AbortController(); fetch(url, { signal: controller.signal }) .then(r => r.json()) .then(d => setData(d)) .catch(e => { if (e.name !== 'AbortError') throw e; }); return () => controller.abort(); // отмена при unmount или смене url }, [url]); return data;}
// Поддержка AbortSignal в собственном async APIasync function* streamData(signal) { signal.throwIfAborted(); // проверка в начале for await (const chunk of source) { signal.throwIfAborted(); // проверка на каждой итерации yield processChunk(chunk); }}Итог: AbortController — стандартный способ отменить fetch и другие async операции; signal.aborted + событие abort позволяют строить цепочки отмены с корректным cleanup.