AbortController - это встроенный объект для отмены асинхронных операций.
Чаще всего его используют вместе с fetch(), когда запрос больше не нужен:
- пользователь ушёл со страницы
- пользователь нажал кнопку отмены
- поисковый запрос изменился, и старый ответ уже неактуален
- запрос выполняется слишком долго
Главная идея: вы создаёте controller, передаёте его signal в операцию, а потом можете вызвать abort().
const controller = new AbortController();
fetch("https://learn-javascript.ru/api/users", {
signal: controller.signal,
});
controller.abort();
Синтаксис
const controller = new AbortController();
У объекта есть две главные части:
controller.signal- сигнал, который передают вfetch()или другую операциюcontroller.abort()- метод, который отменяет операцию
const controller = new AbortController();
console.log(controller.signal.aborted); // false
controller.abort();
console.log(controller.signal.aborted); // true
signal.aborted показывает, был ли уже вызван abort().
Отмена fetch-запроса
Чтобы fetch() можно было отменить, передайте signal во второй аргумент.
const controller = new AbortController();
const responsePromise = fetch("https://learn-javascript.ru/api/users", {
signal: controller.signal,
});
controller.abort();
После вызова abort() запрос будет отменён на стороне JavaScript-кода.
Обычно такой код пишут с обработкой ошибки:
const controller = new AbortController();
try {
const response = await fetch("https://learn-javascript.ru/api/users", {
signal: controller.signal,
});
const users = await response.json();
console.log(users);
} catch (error) {
if (error.name === "AbortError") {
console.log("Запрос отменён");
} else {
console.log("Ошибка запроса");
}
}
Если запрос отменён через AbortController, fetch() отклонит промис с ошибкой AbortError.
Кнопка отмены
Пример с кнопкой:
const controller = new AbortController();
const cancelButton = document.querySelector(".cancel-button");
cancelButton.addEventListener("click", () => {
controller.abort();
});
try {
const response = await fetch("https://learn-javascript.ru/api/lessons", {
signal: controller.signal,
});
const lessons = await response.json();
console.log(lessons);
} catch (error) {
if (error.name === "AbortError") {
console.log("Загрузка отменена пользователем");
}
}
AbortController здесь связывает кнопку и запрос: кнопка вызывает abort(), а fetch() получает сигнал отмены.
Таймаут для fetch()
У fetch() нет обычной настройки timeout, поэтому таймаут часто делают через AbortController.
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => {
controller.abort();
}, timeout);
try {
const response = await fetch(url, {
signal: controller.signal,
});
return response;
} finally {
clearTimeout(timeoutId);
}
}
Использование:
try {
const response = await fetchWithTimeout(
"https://learn-javascript.ru/api/articles",
3000,
);
const articles = await response.json();
console.log(articles);
} catch (error) {
if (error.name === "AbortError") {
console.log("Запрос выполнялся слишком долго");
} else {
console.log("Не удалось загрузить данные");
}
}
finally нужен, чтобы очистить таймер, если запрос завершился раньше таймаута.
Отмена предыдущего запроса
AbortController полезен для поиска по мере ввода.
Если пользователь быстро печатает, старые запросы уже не нужны. Их можно отменять перед новым запросом.
let searchController;
async function searchLessons(query) {
if (searchController) {
searchController.abort();
}
searchController = new AbortController();
const url = new URL("https://learn-javascript.ru/api/search");
url.searchParams.set("q", query);
try {
const response = await fetch(url, {
signal: searchController.signal,
});
const result = await response.json();
console.log(result);
} catch (error) {
if (error.name !== "AbortError") {
console.log("Ошибка поиска");
}
}
}
Когда вызывается новый searchLessons(), предыдущий запрос отменяется.
Это помогает избежать ситуации, когда старый медленный ответ приходит позже нового и перезаписывает актуальные данные.
Один controller нельзя использовать повторно
После вызова abort() сигнал навсегда остаётся отменённым.
const controller = new AbortController();
controller.abort();
console.log(controller.signal.aborted); // true
Если передать такой signal в новый fetch(), запрос сразу будет считаться отменённым.
fetch("https://learn-javascript.ru/api/users", {
signal: controller.signal,
}); // запрос сразу отменится
Для нового запроса создавайте новый controller.
const nextController = new AbortController();
fetch("https://learn-javascript.ru/api/users", {
signal: nextController.signal,
});
Один signal для нескольких запросов
Один и тот же signal можно передать в несколько операций.
const controller = new AbortController();
const userRequest = fetch("https://learn-javascript.ru/api/user", {
signal: controller.signal,
});
const settingsRequest = fetch("https://learn-javascript.ru/api/settings", {
signal: controller.signal,
});
controller.abort();
В этом случае abort() отменит оба запроса.
Это удобно, когда нужно отменить сразу всю группу загрузок, например при уходе со страницы.
abort() не откатывает действие на сервере
AbortController отменяет ожидание результата на стороне клиента.
Но если запрос уже дошёл до сервера, сервер мог начать обработку.
const controller = new AbortController();
fetch("https://learn-javascript.ru/api/profile", {
method: "POST",
body: JSON.stringify({ name: "Анна" }),
signal: controller.signal,
});
controller.abort();
Такой вызов не гарантирует, что сервер ничего не успел сделать.
Поэтому AbortController особенно хорошо подходит для отмены загрузки данных, поиска, автодополнения и других запросов, где результат просто стал не нужен.
Частые ошибки
Создать controller, но не передать signal
const controller = new AbortController();
fetch("https://learn-javascript.ru/api/users");
controller.abort();
Такой abort() не отменит запрос, потому что fetch() не получил controller.signal.
Правильно:
fetch("https://learn-javascript.ru/api/users", {
signal: controller.signal,
});
Повторно использовать отменённый controller
const controller = new AbortController();
controller.abort();
fetch("https://learn-javascript.ru/api/users", {
signal: controller.signal,
}); // сразу отменится
После отмены создавайте новый AbortController.
Считать отмену обычной ошибкой запроса
try {
await fetch("https://learn-javascript.ru/api/users", {
signal: controller.signal,
});
} catch (error) {
console.log("Ошибка");
}
Лучше отдельно проверять AbortError, чтобы не показывать пользователю ошибку там, где он сам отменил действие.
if (error.name === "AbortError") {
console.log("Запрос отменён");
}
Быстрая памятка
AbortControllerнужен для отмены асинхронных операций- для
fetch()вoptionsпередаютsignal - отмена вызывается через
controller.abort() - после отмены
signal.abortedстановитсяtrue - отменённый
fetch()обычно попадает вcatchсAbortError - один controller нельзя использовать повторно после
abort() - для нового запроса создавайте новый
AbortController - один
signalможно передать в несколько запросов - таймаут для
fetch()часто делают черезsetTimeout()иabort() - отмена запроса не гарантирует откат действия на сервере
Коротко
AbortController позволяет отменять fetch() и другие операции через общий сигнал. Создайте controller, передайте controller.signal в запрос, а когда результат больше не нужен, вызовите controller.abort(). В реальном коде важно обрабатывать AbortError, создавать новый controller для нового запроса и помнить, что отмена на клиенте не всегда означает отмену действия на сервере.