JavaScript AbortController

16 июня 2026
Автор

Антон Ларичев

Что такое AbortController

AbortController — это встроенный браузерный API, который позволяет прерывать асинхронные операции: HTTP-запросы через fetch, работу с потоками (ReadableStream), обработчики событий и любой собственный асинхронный код.

До появления этого API не было стандартного способа отменить запрос, уже отправленный в сеть. Разработчикам приходилось вводить флаги-признаки отмены и игнорировать результаты, но сам запрос всё равно отправлялся и потреблял трафик и память. AbortController решает проблему на уровне самого запроса.

АPI состоит из двух частей:

  • AbortController — контроллер, у которого есть метод abort() для отправки сигнала отмены.
  • AbortSignal — объект-сигнал (controller.signal), который передаётся в отменяемую операцию. Он содержит свойство aborted и генерирует событие abort.
const controller = new AbortController();
const signal = controller.signal;

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

controller.abort();

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

Отмена fetch-запроса

Самый распространённый сценарий — прерывание HTTP-запроса. Достаточно передать signal вторым аргументом в fetch:

const controller = new AbortController();

fetch('https://api.example.com/data', { signal: controller.signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Запрос был отменён');
    } else {
      console.error('Сетевая ошибка:', err);
    }
  });

// Отменяем запрос через 2 секунды
setTimeout(() => controller.abort(), 2000);

Когда abort() вызывается до получения ответа, fetch завершается с ошибкой AbortError. Важно отличать её от других ошибок сети, поэтому всегда проверяйте err.name === 'AbortError'.

Использование с async/await

С синтаксисом async/await паттерн выглядит чище:

async function loadUser(userId) {
  const controller = new AbortController();

  // Отменяем, если пользователь ушёл со страницы
  window.addEventListener('beforeunload', () => controller.abort());

  try {
    const response = await fetch(`/api/users/${userId}`, {
      signal: controller.signal,
    });

    if (!response.ok) {
      throw new Error(`HTTP ${response.status}`);
    }

    return await response.json();
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('Загрузка пользователя отменена');
      return null;
    }
    throw err;
  }
}

Отмена нескольких запросов одновременно

Один контроллер может управлять несколькими запросами. Один вызов abort() прерывает их все:

async function loadDashboard() {
  const controller = new AbortController();
  const { signal } = controller;

  const cancelButton = document.getElementById('cancel');
  cancelButton.addEventListener('click', () => controller.abort());

  try {
    const [users, orders, stats] = await Promise.all([
      fetch('/api/users', { signal }).then(r => r.json()),
      fetch('/api/orders', { signal }).then(r => r.json()),
      fetch('/api/stats', { signal }).then(r => r.json()),
    ]);

    return { users, orders, stats };
  } catch (err) {
    if (err.name === 'AbortError') {
      console.log('Загрузка дашборда отменена пользователем');
      return null;
    }
    throw err;
  }
}

Это особенно полезно в SPA, когда пользователь быстро переключается между разделами: при смене маршрута можно отменить все незавершённые запросы предыдущего экрана.

Таймаут через AbortSignal.timeout()

С ES2022 появился статический метод AbortSignal.timeout(ms), который создаёт сигнал с встроенным таймаутом. Код становится значительно короче:

async function fetchWithTimeout(url) {
  try {
    const response = await fetch(url, {
      signal: AbortSignal.timeout(5000), // 5 секунд
    });
    return await response.json();
  } catch (err) {
    if (err.name === 'TimeoutError') {
      console.log('Запрос превысил таймаут');
    } else if (err.name === 'AbortError') {
      console.log('Запрос был отменён вручную');
    } else {
      throw err;
    }
  }
}

Обратите внимание: при срабатывании таймаута ошибка называется TimeoutError, а не AbortError — это важно для корректной обработки.

Комбинирование таймаута и ручной отмены

Часто нужно и ограничить запрос по времени, и предоставить пользователю кнопку отмены. Для этого используют AbortSignal.any() (доступен в современных браузерах):

const userController = new AbortController();

const signal = AbortSignal.any([
  userController.signal,
  AbortSignal.timeout(10000),
]);

try {
  const response = await fetch('/api/heavy-report', { signal });
  const data = await response.json();
  return data;
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('Отменено пользователем');
  } else if (err.name === 'TimeoutError') {
    console.log('Превышен таймаут 10 секунд');
  }
}

// Кнопка отмены
cancelBtn.onclick = () => userController.abort();

Отмена в React-компонентах

В React классический сценарий — отмена запроса при размонтировании компонента. Без отмены обновление состояния после размонтирования вызывает утечки памяти и предупреждения в консоли.

import { useEffect, useState } from 'react';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    const controller = new AbortController();

    async function loadUser() {
      try {
        const response = await fetch(`/api/users/${userId}`, {
          signal: controller.signal,
        });
        const data = await response.json();
        setUser(data);
      } catch (err) {
        if (err.name !== 'AbortError') {
          setError(err.message);
        }
      }
    }

    loadUser();

    return () => controller.abort();
  }, [userId]);

  if (error) return <div>Ошибка: {error}</div>;
  if (!user) return <div>Загрузка...</div>;
  return <div>{user.name}</div>;
}

Функция очистки return () => controller.abort() вызывается при каждом изменении userId и при размонтировании компонента, автоматически отменяя предыдущий незавершённый запрос.

Отмена собственного асинхронного кода

AbortSignal не ограничен только fetch. Его можно использовать в любом асинхронном коде, проверяя signal.aborted или подписываясь на событие abort.

Прерываемая задержка

function delay(ms, signal) {
  return new Promise((resolve, reject) => {
    if (signal?.aborted) {
      return reject(new DOMException('Aborted', 'AbortError'));
    }

    const timer = setTimeout(resolve, ms);

    signal?.addEventListener('abort', () => {
      clearTimeout(timer);
      reject(new DOMException('Aborted', 'AbortError'));
    });
  });
}

const controller = new AbortController();

delay(5000, controller.signal)
  .then(() => console.log('Прошло 5 секунд'))
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Задержка прервана');
    }
  });

setTimeout(() => controller.abort(), 1000); // Прерываем через 1 секунду

Прерываемый цикл обработки данных

async function processItems(items, signal) {
  const results = [];

  for (const item of items) {
    if (signal.aborted) {
      throw new DOMException('Обработка прервана', 'AbortError');
    }

    // Имитация асинхронной обработки каждого элемента
    const result = await processItem(item, signal);
    results.push(result);
  }

  return results;
}

async function processItem(item, signal) {
  const response = await fetch(`/api/process/${item.id}`, { signal });
  return response.json();
}

Передача причины отмены

Метод abort() принимает необязательный аргумент — причину отмены. Она доступна через signal.reason:

const controller = new AbortController();

fetch('/api/data', { signal: controller.signal }).catch(err => {
  if (err.name === 'AbortError') {
    console.log('Причина:', controller.signal.reason);
    // Причина: NavigatedAway
  }
});

controller.abort('NavigatedAway');

Причиной может быть строка, объект ошибки или любое значение — это удобно для диагностики в сложных системах с несколькими источниками отмены.

Типичные ошибки

Повторное использование контроллера после abort(). После вызова abort() сигнал остаётся в состоянии aborted навсегда. Для нового запроса нужен новый контроллер:

// Неправильно
const controller = new AbortController();
controller.abort();
await fetch('/api/data', { signal: controller.signal }); // Сразу упадёт с AbortError

// Правильно
let controller = new AbortController();

function startNewRequest() {
  controller.abort(); // Отменяем предыдущий, если есть
  controller = new AbortController(); // Создаём новый
  return fetch('/api/data', { signal: controller.signal });
}

Игнорирование AbortError. Если не обрабатывать AbortError отдельно, пользователь увидит сообщение об ошибке там, где её быть не должно:

// Неправильно
async function load(signal) {
  const data = await fetch('/api', { signal }).then(r => r.json());
  setData(data); // При отмене здесь упадёт необработанная ошибка
}

// Правильно
async function load(signal) {
  try {
    const data = await fetch('/api', { signal }).then(r => r.json());
    setData(data);
  } catch (err) {
    if (err.name !== 'AbortError') throw err;
    // Отмена — нормальное поведение, просто выходим
  }
}

Не передавать signal глубже по цепочке. Если функция делает несколько вложенных запросов, нужно передать сигнал в каждый из них:

// Неправильно — вложенный fetch не знает о сигнале
async function loadOrder(orderId, signal) {
  const order = await fetch(`/api/orders/${orderId}`, { signal }).then(r => r.json());
  const user = await fetch(`/api/users/${order.userId}`).then(r => r.json()); // Нет signal!
  return { order, user };
}

// Правильно
async function loadOrder(orderId, signal) {
  const order = await fetch(`/api/orders/${orderId}`, { signal }).then(r => r.json());
  const user = await fetch(`/api/users/${order.userId}`, { signal }).then(r => r.json());
  return { order, user };
}

Поддержка в браузерах и Node.js

AbortController поддерживается во всех современных браузерах и в Node.js начиная с версии 15. В Node.js 18+ AbortSignal.timeout() также доступен нативно. Для старых окружений существуют полифиллы, например abortcontroller-polyfill.

npm install abortcontroller-polyfill
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only';

Итог

AbortController — стандартный и надёжный способ управлять жизненным циклом асинхронных операций в JavaScript. Основные правила:

  • Создавайте новый контроллер для каждого независимого набора запросов.
  • Всегда отдельно обрабатывайте AbortError, чтобы не показывать пользователю ложные ошибки.
  • Передавайте signal во все вложенные асинхронные вызовы.
  • Используйте AbortSignal.timeout() для краткого синтаксиса таймаута.
  • В React очищайте запросы в функции очистки useEffect.

Глубже освоить асинхронный JavaScript, включая работу с fetch, промисами и управлением состоянием запросов, можно на курсе PurpleSchool.

Курс по JavaScript на PurpleSchool

Стрелочка влевоasync/await в JavaScript

Постройте личный план изучения Javascript до уровня Middle — бесплатно!

Javascript — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Javascript

Как работает метод trim() - JavaScriptКак работает метод toUpperCase() - JavaScriptКак работает метод toLowerCase() - JavaScriptКак работает метод substring() - JavaScriptКак работает метод startsWith() - JavaScriptКак работает метод split() - JavaScriptКак работает метод slice() - JavaScriptКак работает метод search() - JavaScriptКак работает метод replaceAll() - JavaScriptКак работает метод replace() - JavaScriptКак работает метод repeat() - JavaScriptКак работает метод padStart() - JavaScriptКак работает метод padEnd() - JavaScriptКак работает метод matchAll() - JavaScriptКак работает метод match() - JavaScriptКак работает метод localeCompare() - JavaScriptКак работает свойство length - JavaScriptКак работает метод lastIndexOf() - JavaScriptКак работает метод indexOf() - JavaScriptКак работает метод includes() - JavaScriptКак работает метод fromCodePoint() - JavaScriptКак работает метод fromCharCode() - JavaScriptКак работает метод endsWith() - JavaScriptКак работает метод concat() - JavaScriptКак работает метод codePointAt() - JavaScriptКак работает метод charCodeAt() - JavaScriptКак работает метод charAt() - JavaScript
Итератор в JavaScript
try...catch в JavaScriptError в JavaScript
Событие wheel в JavaScriptСобытие unload в JavaScriptСобытие touch в JavaScriptСобытие submit в JavaScriptСобытие scroll в JavaScriptСобытие reset в JavaScriptМетод .preventDefault() в JavaScriptСобытие mouseover в JavaScriptСобытие mouseout в JavaScriptСобытие load в JavaScriptСобытие keyup в JavaScriptСобытие keydown в JavaScriptСобытие invalid в JavaScriptСобытие input в JavaScriptСобытийная модель Event в JavaScriptОбъект события Event в JavaScriptСобытие DOMContentLoaded в JavaScriptСобытие dblclick в JavaScriptСобытие click в JavaScriptСобытие change в JavaScriptСобытие beforeunload в JavaScript
Как работает метод some() - JavaScriptКак работает метод reverse() - JavaScriptКак работает метод reduce() - JavaScriptКак работает метод map() - JavaScriptКак работает метод isArray() - JavaScriptКак работает метод indexOf() - JavaScriptКак работает метод includes() - JavaScriptКак работает метод from() - JavaScriptКак работает метод forEach() - JavaScriptКак работает метод flatMap() - JavaScriptКак работает метод flat() - JavaScriptКак работает метод findIndex() - JavaScriptКак работает метод find() - JavaScriptКак работает метод filter() - JavaScriptКак работает метод every() - JavaScriptМассивы в JavaScript
Открыть базу знаний

Лучшие курсы по теме

изображение курса

Основы JavaScript

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий