Learn JavaScript

AbortSignal - сигнал отмены в JavaScript

AbortSignal - это объект-сигнал, через который одна часть кода сообщает другой: “операцию нужно отменить”.

Чаще всего AbortSignal получают из AbortController и передают в fetch().

const controller = new AbortController();
const signal = controller.signal;

fetch("https://learn-javascript.ru/api/users", {
  signal,
});

controller.abort();

AbortController управляет отменой, а AbortSignal передаётся туда, где эту отмену нужно учитывать.

Как получить AbortSignal

Обычно AbortSignal не создают напрямую. Его берут из свойства signal у AbortController.

const controller = new AbortController();

console.log(controller.signal); // AbortSignal

В простом виде связка выглядит так:

const controller = new AbortController();

const signal = controller.signal;

controller.abort();

console.log(signal.aborted); // true

Метод abort() вызывают у controller, а не у signal.

Передача signal в fetch()

Чтобы запрос можно было отменить, signal передают в настройки fetch().

const controller = new AbortController();

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("Запрос отменён");
  }
}

Если позже вызвать controller.abort(), fetch() получит сигнал отмены и отклонит промис.

controller.abort();

Свойство aborted

Свойство aborted показывает, был ли сигнал уже отменён.

const controller = new AbortController();
const signal = controller.signal;

console.log(signal.aborted); // false

controller.abort();

console.log(signal.aborted); // true

Это удобно, если ваша функция должна заранее проверить, не отменили ли работу.

function doSomething(signal) {
  if (signal.aborted) {
    return;
  }

  console.log("Выполняем работу");
}

aborted только читают. Изменить его вручную нельзя.

Свойство reason

reason хранит причину отмены.

const controller = new AbortController();

controller.abort("Пользователь отменил загрузку");

console.log(controller.signal.reason);
// "Пользователь отменил загрузку"

Причиной может быть строка, объект или другое значение.

const controller = new AbortController();

controller.abort({
  type: "navigation",
  message: "Пользователь ушёл со страницы",
});

console.log(controller.signal.reason.type); // "navigation"

Если причина не нужна, можно вызывать abort() без аргументов.

Событие abort

AbortSignal умеет отправлять событие abort.

const controller = new AbortController();
const signal = controller.signal;

signal.addEventListener("abort", () => {
  console.log("Операция отменена");
});

controller.abort();

Так можно подключить отмену к своему коду, а не только к fetch().

Например:

function startTimer(signal) {
  const timerId = setInterval(() => {
    console.log("Работаем...");
  }, 1000);

  signal.addEventListener("abort", () => {
    clearInterval(timerId);
    console.log("Таймер остановлен");
  });
}

const controller = new AbortController();

startTimer(controller.signal);

controller.abort();

Когда сигнал отменяется, обработчик очищает таймер.

throwIfAborted()

Метод throwIfAborted() выбрасывает причину отмены, если сигнал уже отменён.

const controller = new AbortController();

controller.abort("Операция отменена");

controller.signal.throwIfAborted(); // выбросит "Операция отменена"

Этот метод удобен внутри своих асинхронных функций.

async function loadData(signal) {
  signal.throwIfAborted();

  const response = await fetch("https://learn-javascript.ru/api/data", {
    signal,
  });

  signal.throwIfAborted();

  return await response.json();
}

Так функция может быстро остановиться, если отмена уже произошла.

AbortSignal.timeout()

В современных браузерах можно создать сигнал, который отменится сам через заданное время.

const signal = AbortSignal.timeout(3000);

try {
  const response = await fetch("https://learn-javascript.ru/api/articles", {
    signal,
  });

  const articles = await response.json();

  console.log(articles);
} catch (error) {
  console.log("Запрос отменён или завершился ошибкой");
}

Так можно сделать таймаут без ручного setTimeout() и clearTimeout().

Если нужно поддерживать старое окружение, таймаут можно сделать через обычный AbortController.

const controller = new AbortController();

const timeoutId = setTimeout(() => {
  controller.abort();
}, 3000);

try {
  await fetch("https://learn-javascript.ru/api/articles", {
    signal: controller.signal,
  });
} finally {
  clearTimeout(timeoutId);
}

AbortSignal.any()

AbortSignal.any() создаёт новый сигнал, который отменится, когда отменится любой сигнал из списка.

const controller = new AbortController();
const timeoutSignal = AbortSignal.timeout(5000);

const signal = AbortSignal.any([controller.signal, timeoutSignal]);

fetch("https://learn-javascript.ru/api/search", {
  signal,
});

Теперь запрос можно отменить вручную:

controller.abort();

или он отменится сам по таймауту.

Это полезно, когда у операции есть несколько причин для отмены: пользователь нажал кнопку, страница закрылась, истекло время ожидания.

Один signal нельзя сбросить обратно

Если сигнал отменён, он остаётся отменённым.

const controller = new AbortController();
const signal = controller.signal;

controller.abort();

console.log(signal.aborted); // true

Такой сигнал нельзя “включить заново”.

Для новой операции создавайте новый AbortController и новый signal.

const nextController = new AbortController();

fetch("https://learn-javascript.ru/api/users", {
  signal: nextController.signal,
});

Частые ошибки

Вызывать abort() у signal

const controller = new AbortController();

controller.signal.abort(); // TypeError

Отмену запускает controller.

controller.abort();

Передать уже отменённый signal в новый запрос

const controller = new AbortController();

controller.abort();

fetch("https://learn-javascript.ru/api/users", {
  signal: controller.signal,
}); // запрос сразу отменится

Для нового запроса нужен новый controller.

Проверять только aborted и забывать про catch

const controller = new AbortController();

if (!controller.signal.aborted) {
  fetch("https://learn-javascript.ru/api/users", {
    signal: controller.signal,
  });
}

Сигнал может отмениться уже после старта запроса. Поэтому для fetch() всё равно нужна обработка catch.

Быстрая памятка

  • AbortSignal обычно получают через controller.signal
  • signal передают в fetch() и другие операции, которые умеют отменяться
  • отмену запускает controller.abort()
  • signal.aborted показывает, был ли сигнал отменён
  • signal.reason хранит причину отмены
  • событие abort можно слушать через addEventListener()
  • throwIfAborted() выбрасывает причину, если сигнал уже отменён
  • AbortSignal.timeout(ms) создаёт сигнал с автоматической отменой
  • AbortSignal.any([...]) объединяет несколько сигналов
  • отменённый signal нельзя сбросить обратно

Коротко

AbortSignal - это объект, который передаёт сигнал отмены в fetch() и другой асинхронный код. Его обычно берут из AbortController, передают в операцию, а затем отменяют через controller.abort(). По самому signal можно проверить aborted, прочитать reason, подписаться на событие abort и построить более аккуратную логику отмены.