Перейти к содержимому

AbortController / AbortSignal

AbortController — стандартный механизм отмены async-операций в браузере и Node.js 15+; правильное использование с fetch, таймаутами и cleanup предотвращает memory leaks, zombie-запросы и race conditions.

// Базовое использование с fetch
const 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 API
async function* streamData(signal) {
signal.throwIfAborted(); // проверка в начале
for await (const chunk of source) {
signal.throwIfAborted(); // проверка на каждой итерации
yield processChunk(chunk);
}
}

Итог: AbortController — стандартный способ отменить fetch и другие async операции; signal.aborted + событие abort позволяют строить цепочки отмены с корректным cleanup.