Что такое AbortController и AbortSignal в JavaScript?

MiddleJavaScript · Frontend·Обновлено 10 августа 2026
Коротко
AbortController — это API для отмены асинхронных операций (fetch-запросов, промисов). Он предоставляет объект AbortSignal, который передаётся в операцию и позволяет прервать её выполнение извне.

AbortController и AbortSignal

AbortController — это встроенный браузерный (и Node.js) API, который позволяет отменять асинхронные операции. Он был введён вместе с Fetch API и решает проблему отсутствия нативного механизма отмены промисов.

Как это работает

При создании AbortController он автоматически создаёт связанный объект AbortSignal, доступный через свойство .signal. Этот сигнал передаётся в операцию, которую нужно сделать отменяемой. Когда вызывается метод .abort() на контроллере, сигнал переходит в состояние aborted: true и генерирует событие abort.

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

console.log(signal.aborted); // false
controller.abort();
console.log(signal.aborted); // true

Использование с fetch

Наиболее распространённый сценарий — отмена HTTP-запросов:

const controller = new AbortController();

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

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

Важно различать AbortError и другие ошибки, чтобы не показывать пользователю ложные сообщения об ошибках.

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

С ES2022 появился удобный статический метод для создания сигнала с таймаутом:

// Автоматически отменит запрос через 5 секунд
fetch('/api/data', { signal: AbortSignal.timeout(5000) });

Использование в пользовательских асинхронных операциях

AbortController можно использовать не только с fetch — любая асинхронная операция может поддерживать отмену:

function delay(ms, signal) {
  return new Promise((resolve, reject) => {
    const timer = setTimeout(resolve, ms);

    signal?.addEventListener('abort', () => {
      clearTimeout(timer);
      reject(new DOMException('Операция отменена', 'AbortError'));
    });
  });
}

const controller = new AbortController();
delay(5000, controller.signal)
  .then(() => console.log('Готово'))
  .catch(err => console.log(err.message));

controller.abort(); // Отменяем немедленно

Объединение нескольких сигналов

В современных средах можно объединить несколько сигналов с помощью AbortSignal.any():

const userController = new AbortController();
const timeoutSignal = AbortSignal.timeout(10000);

// Отменится при первом сработавшем сигнале
const combinedSignal = AbortSignal.any([
  userController.signal,
  timeoutSignal
]);

fetch('/api/data', { signal: combinedSignal });

Практический паттерн в React

Очень часто AbortController используется в useEffect для отмены запросов при размонтировании компонента:

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

  fetch('/api/user', { signal: controller.signal })
    .then(r => r.json())
    .then(setUser)
    .catch(err => {
      if (err.name !== 'AbortError') setError(err);
    });

  return () => controller.abort(); // Очистка при размонтировании
}, [userId]);

Что хочет услышать интервьюер

Понимание назначения AbortController — отмена асинхронных операций, прежде всего fetch-запросов

Знание связки AbortController ↔ AbortSignal: контроллер управляет, сигнал передаётся в операцию

Умение различать AbortError от других ошибок в catch-блоке

Знакомство с практическими сценариями: отмена при размонтировании React-компонента, debounce запросов, таймауты

Понимание, что один контроллер можно использовать для отмены нескольких запросов одновременно

Пример: Базовое использование с fetch и обработка отмены

async function fetchUserData(userId: string, signal: AbortSignal) {
  const response = await fetch(`/api/users/${userId}`, { signal });

  if (!response.ok) {
    throw new Error(`HTTP ошибка: ${response.status}`);
  }

  return response.json();
}

// Использование
const controller = new AbortController();

fetchUserData('42', controller.signal)
  .then(user => console.log('Пользователь:', user))
  .catch((err: Error) => {
    if (err.name === 'AbortError') {
      console.log('Запрос отменён пользователем');
    } else {
      console.error('Ошибка загрузки:', err.message);
    }
  });

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

Пример: Паттерн в React: отмена при смене зависимости

import { useEffect, useState } from 'react';

interface User {
  id: string;
  name: string;
}

function useUser(userId: string) {
  const [user, setUser] = useState<User | null>(null);
  const [error, setError] = useState<string | null>(null);
  const [loading, setLoading] = useState(false);

  useEffect(() => {
    // Создаём контроллер внутри эффекта
    const controller = new AbortController();

    setLoading(true);
    setError(null);

    fetch(`/api/users/${userId}`, { signal: controller.signal })
      .then(res => {
        if (!res.ok) throw new Error('Ошибка сервера');
        return res.json() as Promise<User>;
      })
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch((err: Error) => {
        // Игнорируем AbortError — это штатная отмена
        if (err.name !== 'AbortError') {
          setError(err.message);
          setLoading(false);
        }
      });

    // Отменяем запрос при смене userId или размонтировании
    return () => controller.abort();
  }, [userId]);

  return { user, error, loading };
}

Пример: Отменяемая пользовательская асинхронная операция

// Реализация задержки с поддержкой отмены
function cancellableDelay(ms: number, signal?: AbortSignal): Promise<void> {
  return new Promise((resolve, reject) => {
    // Если сигнал уже отменён — немедленно отклоняем
    if (signal?.aborted) {
      return reject(new DOMException('Уже отменено', 'AbortError'));
    }

    const timer = setTimeout(resolve, ms);

    signal?.addEventListener('abort', () => {
      clearTimeout(timer);
      reject(new DOMException('Задержка отменена', 'AbortError'));
    }, { once: true }); // once: true — автоматически снимает обработчик
  });
}

// Использование с AbortSignal.timeout()
async function pollStatus(signal: AbortSignal) {
  while (!signal.aborted) {
    const status = await fetch('/api/status', { signal }).then(r => r.json());

    if (status.done) break;

    // Ждём 2 секунды перед следующим опросом
    await cancellableDelay(2000, signal);
  }
}

// Опрос с автоматическим таймаутом 30 секунд
pollStatus(AbortSignal.timeout(30_000))
  .catch(err => {
    if (err.name === 'TimeoutError') console.log('Истекло время ожидания');
    else if (err.name === 'AbortError') console.log('Опрос отменён');
  });

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

Не обрабатывают AbortError отдельно, показывая пользователю ошибку там, где запрос был намеренно отменён

Создают новый AbortController на каждый рендер в React вместо того, чтобы создавать его внутри useEffect

Забывают вызвать controller.abort() в функции очистки useEffect, что приводит к утечкам и race condition

Путают AbortController с механизмом отмены промисов — промисы сами по себе нельзя отменить, AbortController лишь сигнализирует об отмене

Не знают о современных статических методах AbortSignal.timeout() и AbortSignal.any(), предлагая более громоздкие решения

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

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

TypeScript с нуля

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

Feature-Sliced Design

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

Next.js - с нуля

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