AbortController в JavaScript
- понедельник, 10 марта 2025 г. в 00:00:04
Привет, Хабр!
Сегодня мы рассмотрим интересный инструмент в JS. AbortController в JS — инструмент, который позволяет отменять асинхронные операции в любой момент. Разберёмся, как он работает, где пригодится и какие у него есть проблемы.
AbortController — это инструмент для принудительной остановки асинхронных операций в JavaScript.
Например, можно:
1. Остановить fetch()
‑запрос, если он уже не нужен.
2. Прервать таймер (setTimeout()
, setInterval()
).
3. Отменить стриминг данных (ReadableStream
).
Как работает AbortController:
Создаём новый контроллер: const controller = new AbortController();
Получаем сигнал (signal): const signal = controller.signal;
Передаём signal в асинхронную операцию (fetch
, setTimeout
и т. д.)
Вызываем .abort()
, когда нужно прервать операцию.
AbortController.signal
Возвращает объект AbortSignal
, который сообщает, когда асинхронная операция должна быть прервана.
const controller = new AbortController();
console.log(controller.signal); // AbortSignal { aborted: false }
AbortController.abort()
Прерывает все операции, использующие signal. После этого signal.aborted === true
.
const controller = new AbortController();
controller.abort();
console.log(controller.signal.aborted); // true
signal.addEventListener(“abort”, callback)
Можно подписаться на событие abort и выполнить действия при отмене.
const controller = new AbortController();
controller.signal.addEventListener("abort", () => console.log("Операция отменена"));
setTimeout(() => controller.abort(), 2000);
Один из самых популярных случаев применения AbortController — управление сетевыми запросами.
Пример без AbortController (плохо):
async function fetchData() {
const response = await fetch("https://api.example.com/data");
const data = await response.json();
console.log(data);
}
fetchData();
setTimeout(() => console.log("Что делать, если запрос уже не нужен?"), 1000);
Этот запрос нельзя отменить. Даже если он больше не нужен, браузер его дождётся до конца.
Используем AbortController (правильно):
const controller = new AbortController();
const signal = controller.signal;
async function fetchData() {
try {
const response = await fetch("https://api.example.com/data", { signal });
const data = await response.json();
console.log(data);
} catch (err) {
if (err.name === "AbortError") {
console.log("Запрос был отменён!");
} else {
console.error("Ошибка:", err);
}
}
}
// Запускаем запрос
fetchData();
// Отменяем его через 1 секунду
setTimeout(() => controller.abort(), 1000);
Теперь запрос не будет тратить ресурсы браузера, если он больше не нужен.
Кейс 1: отмена запросов в поисковой строке
При каждом вводе символа браузер делает новый запрос, но старые продолжают выполняться.
let controller;
async function search(query) {
if (controller) controller.abort(); // Отменяем предыдущий запрос
controller = new AbortController();
const signal = controller.signal;
try {
const response = await fetch(`https://api.example.com/search?q=${query}`, { signal });
const data = await response.json();
console.log("Результаты:", data);
} catch (err) {
if (err.name === "AbortError") {
console.log("Предыдущий запрос отменён");
} else {
console.error("Ошибка:", err);
}
}
}
document.querySelector("#search").addEventListener("input", (e) => {
search(e.target.value);
});
Теперь браузер не тратит ресурсы на старые ненужные запросы.
Кейс 2: остановка таймеров и фоновых задач
AbortController позволяет управлять setTimeout()
и останавливать фоновые операции.
const controller = new AbortController();
const signal = controller.signal;
function delayedTask() {
if (signal.aborted) {
console.log("Таймер отменён");
return;
}
setTimeout(() => {
if (!signal.aborted) {
console.log("Задача выполнена");
}
}, 5000);
}
// Останавливаем выполнение через 2 секунды
setTimeout(() => controller.abort(), 2000);
delayedTask();
Таймер не выполнится, если .abort()
вызван раньше.
Кейс 3: отмена загрузки файлов (стриминг данных)
AbortController работает с ReadableStream, позволяя прерывать загрузку файлов.
const controller = new AbortController();
const signal = controller.signal;
async function fetchLargeFile() {
try {
const response = await fetch("https://example.com/largefile.zip", { signal });
const reader = response.body.getReader();
let receivedLength = 0;
let chunks = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
receivedLength += value.length;
console.log(`Загружено ${receivedLength} байт`);
}
} catch (err) {
if (err.name === "AbortError") {
console.log("Загрузка отменена");
} else {
console.error("Ошибка:", err);
}
}
}
document.querySelector("#download").addEventListener("click", fetchLargeFile);
document.querySelector("#abort").addEventListener("click", () => controller.abort());
Если нажать «Отмена», загрузка файла прервётся, а браузер освободит ресурсы.
AbortController нельзя переиспользовать после .abort()
const controller = new AbortController();
controller.abort();
controller.abort(); // Ошибка, сигнал уже отменён
Поэтому создавайте новый AbortController для каждой операции.
Не все API поддерживают AbortSignal
Например, XMLHttpRequest не работает с AbortController.
Обработку AbortError надо учитывать
fetch(url, { signal })
.catch(err => {
if (err.name === "AbortError") {
console.log("Запрос отменён");
} else {
console.error("Ошибка:", err);
}
});
А какие задачи с AbortController решали вы? Делитесь в комментариях!
19 марта пройдет открытый урок на тему «Прототипное наследование в JavaScript». Записаться бесплатно можно на странице курса "Fullstack developer".
Все темы открытых уроков можно посмотреть в календаре.