Как отменить асинхронную операцию? (AbortController)

👨‍💻 Frontend Developer 🟠 Может встретиться 🎚️ Средний
#JavaScript #Асинхронность #База JS

Краткий ответ

AbortController — это встроенный в JavaScript механизм для отмены асинхронных операций. Он позволяет создать сигнал отмены, который можно передать в асинхронные операции (fetch, setTimeout и др.), чтобы прервать их выполнение. Это особенно полезно для отмены HTTP-запросов, таймеров и других длительных операций.

Основные компоненты:

  • AbortController — создает контроллер отмены
  • signal — сигнал отмены, передаваемый в операции
  • abort() — метод для инициации отмены

Полный ответ

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

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

AbortController создает сигнал отмены, который можно передать в асинхронные операции:

const controller = new AbortController();
const { signal } = controller;
 
// Передаем сигнал в асинхронную операцию
fetch('/api/data', { signal });
 
// Отменяем операцию
controller.abort();

Основные сценарии использования

1. Отмена HTTP-запросов

Самый распространенный случай использования:

const controller = new AbortController();
 
fetch('/api/long-operation', { 
  signal: controller.signal 
})
.then(response => response.json())
.catch(error => {
  if (error.name === 'AbortError') {
    console.log('Запрос отменен');
  }
});
 
// Отмена запроса
controller.abort();

2. Отмена таймеров

Можно отменять таймеры с помощью AbortController:

function delay(ms, signal) {
  return new Promise((resolve, reject) => {
    const timeoutId = setTimeout(resolve, ms);
    
    // Слушаем сигнал отмены
    signal.addEventListener('abort', () => {
      clearTimeout(timeoutId);
      reject(new DOMException('Операция отменена', 'AbortError'));
    });
  });
}

Практические примеры

Отмена запроса по таймауту

function fetchWithTimeout(url, timeoutMs) {
  const controller = new AbortController();
  
  // Отмена по таймауту
  const timeoutId = setTimeout(() => {
    controller.abort();
  }, timeoutMs);
  
  return fetch(url, { signal: controller.signal })
    .finally(() => clearTimeout(timeoutId));
}

Отмена при переходе на другую страницу

const controller = new AbortController();
 
// При переходе отменяем все запросы
window.addEventListener('beforeunload', () => {
  controller.abort();
});
 
fetch('/api/data', { signal: controller.signal });

Работа с сигналом отмены

Проверка состояния сигнала

const controller = new AbortController();
const { signal } = controller;
 
// Проверяем, отменен ли сигнал
if (signal.aborted) {
  console.log('Операция уже отменена');
}

Слушаем событие отмены

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

Распространенные ошибки и решения

1. Игнорирование ошибок отмены

// ❌ Игнорируем ошибки отмены
fetch('/api/data', { signal })
  .then(response => console.log(response));
 
// ✅ Правильно обрабатываем ошибки
fetch('/api/data', { signal })
  .then(response => console.log(response))
  .catch(error => {
    if (error.name === 'AbortError') {
      // Ожидаемая отмена, не ошибка
      console.log('Запрос отменен');
    } else {
      // Реальная ошибка
      console.error('Ошибка запроса:', error);
    }
  });

2. Отмена уже завершенных операций

Вызов abort() на уже завершенных операциях не вызывает ошибок, но и не имеет эффекта.

Лучшие практики

  1. Всегда обрабатывайте AbortError — это ожидаемое поведение, а не ошибка
  2. Используйте один контроллер для связанных операций — для групповой отмены
  3. Очищайте таймеры при отмене — избегайте утечек памяти
  4. Не переиспользуйте AbortController — создавайте новый для каждой операции
  5. Отменяйте операции при уходе пользователя — улучшает производительность

Совместимость

AbortController поддерживается всеми современными браузерами. Для старых браузеров требуется полифил.

Ключевые преимущества AbortController

  1. Стандартизированный подход — единый способ отмены для всех асинхронных операций
  2. Универсальность — работает с fetch, таймерами и пользовательскими операциями
  3. Простота использования — интуитивный API
  4. Экономия ресурсов — предотвращает выполнение ненужных операций

AbortController — это мощный инструмент для управления асинхронными операциями, позволяющий эффективно отменять запросы и освобождать ресурсы.


Задача для проверки знаний

Задача

Что произойдет при выполнении этого кода и как его улучшить?

const controller = new AbortController();
 
fetch('/api/data', { signal: controller.signal })
  .then(response => response.json())
  .then(data => console.log(data));
 
// Через 2 секунды отменяем
setTimeout(() => {
  controller.abort();
}, 2000);
Посмотреть ответ

Ответ: При выполнении этого кода:

  1. Начнется HTTP-запрос к /api/data
  2. Через 2 секунды вызовется controller.abort()
  3. Если запрос еще не завершен, он будет отменен
  4. Но в коде нет обработки ошибки отмены, поэтому при отмене возникнет необработанная ошибка

Улучшенная версия:

const controller = new AbortController();
 
fetch('/api/data', { signal: controller.signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Запрос был отменен');
    } else {
      console.error('Ошибка запроса:', error);
    }
  });
 
// Через 2 секунды отменяем
setTimeout(() => {
  controller.abort();
}, 2000);

Объяснение:

  • При отмене fetch выбрасывает ошибку с именем “AbortError”
  • Без обработки catch эта ошибка остается необработанной
  • Правильная обработка позволяет отличить отмену от реальных ошибок
  • Пользователь получает понятное сообщение о том, что запрос был отменен

Важно понимать, что AbortController — это не мгновенная отмена, а сигнал, который асинхронные операции могут проверять и реагировать на него соответствующим образом.


Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪