javascript

AbortController в JavaScript

  • понедельник, 10 марта 2025 г. в 00:00:04
https://habr.com/ru/companies/otus/articles/886496/

Привет, Хабр!

Сегодня мы рассмотрим интересный инструмент в JS. AbortController в JS — инструмент, который позволяет отменять асинхронные операции в любой момент. Разберёмся, как он работает, где пригодится и какие у него есть проблемы.

AbortController — это инструмент для принудительной остановки асинхронных операций в JavaScript.

Например, можно:

1. Остановить fetch()‑запрос, если он уже не нужен.
2. Прервать таймер (setTimeout(), setInterval()).
3. Отменить стриминг данных (ReadableStream).

Как работает AbortController:

  1. Создаём новый контроллер: const controller = new AbortController();

  2. Получаем сигнал (signal): const signal = controller.signal;

  3. Передаём signal в асинхронную операцию (fetch, setTimeout и т. д.)

  4. Вызываем .abort(), когда нужно прервать операцию.

Основные методы AbortController

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);

Как отменять fetch()-запросы

Один из самых популярных случаев применения 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);

Теперь запрос не будет тратить ресурсы браузера, если он больше не нужен.

Три кейса применения AbortController

Кейс 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());

Если нажать «Отмена», загрузка файла прервётся, а браузер освободит ресурсы.

Возможные проблем

  1. AbortController нельзя переиспользовать после .abort()

    const controller = new AbortController();
    controller.abort();
    controller.abort(); // Ошибка, сигнал уже отменён

    Поэтому создавайте новый AbortController для каждой операции.

  2. Не все API поддерживают AbortSignal
    Например, XMLHttpRequest не работает с AbortController.

  3. Обработку AbortError надо учитывать

    fetch(url, { signal })
      .catch(err => {
        if (err.name === "AbortError") {
          console.log("Запрос отменён");
        } else {
          console.error("Ошибка:", err);
        }
      });

А какие задачи с AbortController решали вы? Делитесь в комментариях!

19 марта пройдет открытый урок на тему «Прототипное наследование в JavaScript». Записаться бесплатно можно на странице курса "Fullstack developer".

Все темы открытых уроков можно посмотреть в календаре.