← Назад к вопросам

Зачем нужен AbortController в Node.js?

1.3 Junior🔥 71 комментариев
#Node.js и JavaScript

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Зачем нужен AbortController в Node.js?

AbortController — это API для отмены асинхронных операций. Он позволяет остановить выполнение длительных операций (fetch, timers, streams), не дожидаясь их завершения.

Проблема, которую решает AbortController

Без AbortController (ПЛОХО):

async function downloadFile(url: string) {
  const response = await fetch(url); // Может загружаться часами
  const data = await response.arrayBuffer();
  return data;
}

// Пользователь отменил запрос
// Но загрузка продолжает идти в фоне! 
// Пустая трата трафика и памяти

С AbortController (ХОРОШО):

const controller = new AbortController();
const timeoutId = setTimeout(() => {
  controller.abort(); // Отменить загрузку после 10 сек
}, 10000);

try {
  const response = await fetch(url, {
    signal: controller.signal
  });
  const data = await response.arrayBuffer();
  return data;
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('Загрузка отменена');
  }
} finally {
  clearTimeout(timeoutId);
}

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

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

// 2. Получаем сигнал для передачи в операцию
const signal = controller.signal;

// 3. Проверяем, отменена ли операция
console.log(signal.aborted); // false

// 4. Отменяем
controller.abort();

// 5. После отмены
console.log(signal.aborted); // true

// 6. Вызывается событие abort
signal.addEventListener('abort', () => {
  console.log('Операция отменена!');
});

controller.abort(); // Логирует: Операция отменена!

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

Пример 1: Timeout для fetch запроса

async function fetchWithTimeout(url: string, timeout: number = 5000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => {
    controller.abort();
  }, timeout);

  try {
    const response = await fetch(url, {
      signal: controller.signal
    });
    return await response.json();
  } catch (err) {
    if (err.name === 'AbortError') {
      throw new Error(`Request timeout after ${timeout}ms`);
    }
    throw err;
  } finally {
    clearTimeout(timeoutId);
  }
}

// Использование
try {
  const data = await fetchWithTimeout('https://api.example.com/users', 3000);
  console.log(data);
} catch (err) {
  console.error(err.message); // Request timeout after 3000ms
}

Пример 2: Отмена при клике на кнопку отмены

class DataFetcher {
  private controller: AbortController | null = null;

  async startDownload(url: string) {
    this.controller = new AbortController();

    try {
      const response = await fetch(url, {
        signal: this.controller.signal
      });
      const data = await response.arrayBuffer();
      console.log(`Downloaded ${data.byteLength} bytes`);
    } catch (err) {
      if (err.name === 'AbortError') {
        console.log('Download cancelled by user');
      } else {
        console.error('Download error:', err);
      }
    }
  }

  cancelDownload() {
    if (this.controller) {
      this.controller.abort();
    }
  }
}

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

// Кнопка "Скачать"
document.getElementById('download-btn')?.addEventListener('click', () => {
  fetcher.startDownload('https://example.com/largefile.zip');
});

// Кнопка "Отмена"
document.getElementById('cancel-btn')?.addEventListener('click', () => {
  fetcher.cancelDownload();
});

Пример 3: Отмена параллельных операций

async function fetchMultipleUsers(userIds: string[]) {
  const controller = new AbortController();

  try {
    const promises = userIds.map(id =>
      fetch(`/api/users/${id}`, {
        signal: controller.signal
      }).then(r => r.json())
    );

    // Если одна операция падает, отменяем все остальные
    const users = await Promise.all(promises);
    return users;
  } catch (err) {
    controller.abort(); // Отменяем оставшиеся запросы
    throw err;
  }
}

Пример 4: AbortController с fs.promises в Node.js

import fs from 'fs/promises';

const controller = new AbortController();

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

try {
  // fs.readFile поддерживает signal в Node.js 15+
  const data = await fs.readFile('large-file.txt', {
    signal: controller.signal
  });
  console.log(data);
} catch (err) {
  if (err.name === 'AbortError') {
    console.log('File read cancelled');
  }
}

Пример 5: AbortController с timeout helper

function withAbortTimeout<T>(
  promise: Promise<T>,
  timeoutMs: number
): Promise<T> {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  return Promise.race([
    promise,
    new Promise<T>((_, reject) => {
      controller.signal.addEventListener('abort', () => {
        reject(new Error(`Operation timeout after ${timeoutMs}ms`));
      });
    })
  ]).finally(() => clearTimeout(timeoutId));
}

// Использование
const result = await withAbortTimeout(
  fetch('/api/slow').then(r => r.json()),
  5000
);

Когда использовать AbortController?

  1. HTTP запросы с таймаутом — fetch может зависнуть
  2. User-initiated cancellation — пользователь нажал "Отмена"
  3. Resource cleanup — остановить потоки, отменить операции
  4. Race conditions — отменить старые запросы при новых
  5. Server shutdown — корректно закрыть все in-flight операции

API, которые поддерживают AbortController

// Fetch API
fetch(url, { signal });

// fs.promises (Node.js 15+)
fs.readFile(path, { signal });
fs.writeFile(path, data, { signal });

// Timers (Node.js 15+)
setTimeout(() => {}, ms, { signal });

// EventTarget.addEventListener
eventTarget.addEventListener('event', handler, { signal });

// XMLHttpRequest (браузер)
xhr.abort(); // абстрактный механизм

// Custom операции
function customOperation(signal: AbortSignal) {
  signal.addEventListener('abort', () => {
    console.log('Operation aborted!');
  });
}

Вывод

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