О чем статья?

В современных веб-приложениях асинхронные операции играют ключевую роль. Однако управление ими может быть сложным, особенно когда нужно отменить задачи, уже отправленные на выполнение. До появления AbortController разработчики прибегали к различным костылям, таким как создание глобальных переменных, которые отслеживали состояние запроса или использование оберток над XMLHttpRequest.

AbortController — это класс, представленный в JavaScript, который позволяет управлять асинхронными операциями, такими как Fetch запросы, Promise, fs, setTimeout и setInterval. С его помощью можно прерывать выполнение асинхронных задач и предотвращать нежелательные побочные эффекты от выполнения задач, которые уже неактуальны. AbortController предоставляет надежный и стандартизированный механизм для управления асинхронными задачами. Он позволяет разработчикам контролировать выполнение асинхронных операций, предотвращать выполнение ненужных запросов и избегать утечек памяти. Кроме того, использование AbortController улучшает производительность и ресурсоемкость веб-приложений. Подробнее об API AbortController и AbortSignal вы может почитать по ссылке.

Перейдем к примерам

Для создания экземпляра AbortController используется конструктор класса:

const controller = new AbortController();

// После создания экземпляра AbortController, можно получить экземпляр AbortSignal, используя свойство signal:

const signal = controller.signal;

// Имитация отмены запроса через 3 секунды

setTimeout(() => {
  controller.abort();
}, 3000);

fetch('https://api.example.com/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch request aborted');
    } else {
      console.error('Fetch request failed:', error);
    }
  });

В этом примере через 3 секунды после начала запроса к API будет вызван метод abort(), что приведет к отмене Fetch запроса. Если запрос не будет завершен в течение 3 секунд, обработчик ошибок перехватит событие отмены и выведет соответствующее сообщение в консоль. Если же запрос успеет завершиться раньше, результат будет обработан и выведен в консоль без отмены.

При использовании AbortController важно правильно обрабатывать возможные ошибки. Когда операция отменяется, она обычно вызывает ошибку AbortError. Это позволяет определить, была ли операция завершена успешно или отменена или завершилась по другой причине.

catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch request aborted');
    } else {
      console.error('Fetch request failed:', error);
    }
)

Следует помнить что не все браузеры и окружения поддерживают AbortController. Для обеспечения обратной совместимости рекомендуется проверять наличие поддержки AbortController перед его использованием:

if ('AbortController' in window) {
  // Используем AbortController
} else {
  // Используем альтернативное решение или продолжаем без отмены операций
}

Отмена Promise

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

    const timeoutId = setTimeout(() => {
      resolve();
    }, duration);

    signal.addEventListener('abort', () => {
      clearTimeout(timeoutId);
      reject(new DOMException('Operation aborted', 'AbortError'));
    });
  });
}

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

delay(5000, signal)
  .then(() => {
    console.log('Promise resolved');
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Promise aborted');
    } else {
      console.error('Promise failed:', error);
    }
  });

// Отменяем промис через 3 секунды

setTimeout(() => {
  controller.abort();
}, 3000);

Отмена setTimeout и setInterval

function createInterval(callback, interval, signal) {
  if (signal.aborted) {
    return;
  }

  const intervalId = setInterval(() => {
    callback();
    if (signal.aborted) {
      clearInterval(intervalId);
    }
  }, interval);

  signal.addEventListener('abort', () => {
    clearInterval(intervalId);
  });
}

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

createInterval(() => {
  console.log('Interval callback executed');
}, 1000, signal);

// Отменяем интервал через 5 секунд

setTimeout(() => {
  controller.abort();
}, 5000);

Управление параллельными и последовательными асинхронными операциями

const urls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'https://api.example.com/data3'
];

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

function fetchWithSignal(url, signal) {
  return fetch(url, { signal }).then(response => response.json());
}

Promise.all(urls.map(url => fetchWithSignal(url, signal)))
  .then(results => {
    console.log('All fetch requests completed:', results);
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('One or more fetch requests aborted');
    } else {
      console.error('One or more fetch requests failed:', error);
    }
  });

// Отменяем все запросы через 3 секунды

setTimeout(() => {
  controller.abort();
}, 3000);

Создание кастомного AbortController с timeout

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

class TimeoutAbortController extends AbortController {
  constructor(timeout) {
    super();
    setTimeout(() => {
      this.abort();
    }, timeout);
  }
}

const controller = new TimeoutAbortController(3000);
const signal = controller.signal;

fetch('https://api.example.com/data', { signal })
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch request aborted');
    } else {
      console.error('Fetch request failed:', error);
    }
  });

Последний пример =)

С версии Node.js 10.0.0, многие функции модуля fs поддерживают промисы и могут использовать AbortController. В этом примере мы используем fs.promises.readFile() с AbortController для отмены чтения файла:

const fs = require('fs').promises;
const { AbortController } = require('abort-controller');

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

async function readWithAbort(path, signal) {
  try {
    const data = await fs.readFile(path, { encoding: 'utf-8', signal });
    console.log('File contents:', data);
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('File read operation aborted');
    } else {
      console.error('File read operation failed:', error);
    }
  }
}

const filePath = './example.txt';

// Отменяем чтение файла через 3 секунды

setTimeout(() => {
  controller.abort();
}, 3000);

readWithAbort(filePath, signal);

Вместо заключения

  1. Используйте AbortController только тогда, когда действительно нужно отменять асинхронные операции. В некоторых случаях альтернативные подходы могут быть более подходящими (например, игнорирование результата, если он неактуален).

  2. Обрабатывайте ошибки, связанные с отменой операций, чтобы предоставить пользователю информацию о том, что произошло и каковы возможные следующие шаги.

  3. Очищайте ресурсы после отмены операции. Например, при использовании setTimeout или setInterval, не забудьте вызвать clearTimeout или clearInterval при отмене.

  4. В случае отсутствия поддержки AbortController, предоставляйте альтернативные решения или информируйте пользователя о возможных ограничениях.

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

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


  1. nikolz
    26.04.2023 04:59
    -5

    "можно прерывать выполнение асинхронных задач и предотвращать нежелательные побочные эффекты от выполнения задач, которые уже неактуальны."

    Полагаю, что синхронные или нет бывают события, а не задачи.

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

    Поэтому , Вы не "прерываете асинхронные задачи", а используете синхронные события,так как создаете их по таймеру, т.е. c известным моментом их наступления.


    1. mayorovp
      26.04.2023 04:59
      +3

      Да нет, речь именно об асинхронных задачах. Точного определения не скажу, но всем кроме вас почему-то понятно о чём речь.


      И задачи тут именно что прерываются, поскольку прекращают своё исполнение при получении того самого синхронного события.


      1. nikolz
        26.04.2023 04:59
        -6

        Не хотел заниматься ликбезом, но придется.

        Асинхронность означает, что мы не знаем точного времени наступления событий и поэтому не может их синхронизировать между собой.

        Задачи - это процессы которые длятся во времени, а события - это начало и окончание выполнения задачи.

        Синхронность или асинхронность относится к событиям т е к моментам времени, а не к отрезкам времени.

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

        Не надо путать кислое с зеленым.


        1. mayorovp
          26.04.2023 04:59

          Синхронность или асинхронность относится к событиям т е к моментам времени, а не к отрезкам времени.

          Синхронность или асинхронность — это многозначный термин, точное значение которого меняется от объекта к которому он приложен.


          О каких таких моментах времени идёт речь, например, в синхронном плавании? Или при синхронизации часов? А в синхронных генераторах?


          А при синхронизации данных? Тут, между прочим, не то что событий, тут времени нету!


          Конкретно в программировании под асинхронностью, обычно, понимают вот эту концепцию:


          Асинхронный ввод-вывод, в информатике, является способ осуществления операций ввода/вывода, позволяющий проводить другую обработку, до того как операция ввода/вывода завершена — https://ru.wikipedia.org/wiki/Асинхронность

          Asynchrony (computer programming), the occurrence of events independent of the main program flow, and ways to deal with such events — https://en.wikipedia.org/wiki/Asynchrony


          1. nikolz
            26.04.2023 04:59
            -3

            Синхронность или нет это всегда события, а не процесс.

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

            Синхронный генератор, это значит , что момент перехода сигнала генератора ноль однозначно определяется моментом аналогичного перехода сигнала синхронизирующего генератора.

            Асинхронный ввод-вывод означает, что мы априори не знаем в какой момент времени придут данные или их надо передать, т.е. не знаем момента наступления события.

            т е синхронность и асинхронность всегда связаны с моментом времени.

            И это всегда, применительно к предметной области, однозначно.

            Многозначность там, где присутствует поверхностное понимание предмета.

            Неоднозначность в понятиях приводит к путанице в мыслях.


            1. mayorovp
              26.04.2023 04:59

              Только вот определения этих терминов не совпадают с тем что вы назвали:


              Синхронное плавание — водный вид спорта, связанный с выполнением в воде различных фигур под музыку.

              Синхронный генератор — это генератор, частота вращения ротора которого равна частоте вращения электромагнитого поля в воздушном зазоре.

              Понимаете? Хоть вы и "вывели" их в своих рассуждениях из моментов времени, напрямую в общепринятых определениях моменты времени никак не участвуют.


              Так почему вы отказываете программистам в праве аналогично "вывести" определение асинхронной задачи?


              PS всё ещё жду от вас объяснение синхронизации данных


              Неоднозначность в понятиях приводит к путанице в мыслях.

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


              1. nikolz
                26.04.2023 04:59
                -2

                1. mayorovp
                  26.04.2023 04:59

                  Что вы хотели сказать вашей ссылкой?


                  Я вот, например, вижу там использование термина "асинхронное вычисление", который является синонимом для "асинхронной задачи".


        1. nin-jin
          26.04.2023 04:59
          +1

          Синхронные задачи возвращают управление в момент своего завершения, то есть синхронно. У асинхронных задач эти два события возникают в разное время, то есть асинхронно.


          1. nikolz
            26.04.2023 04:59
            -3

            Любая задача, если в ней нет ошибок, возвращает управление в момент своего завершения.

            Но даже если взять Ваше определение, то Вы рассматриваете два события:

            завершение задачи и возврат управления.

            Т.е. синхронность или асинхронность всегда связана с событиями, а не с процессом.

            В программировании асинхронность это не обязательно совпадение во времени двух событий.

            Например ввод с клавиатуры человеком.

            Для программирования данное событие будет асинхронным, если мы априори не знаем, когда человек нажмет на клавишу.

            Но событие будет синхронным, если мы знаем что ровно в 10 часов нажмет на клавишу.

            Т е в данном случае событие становится синхронным со временем.

            Если берутся два события, то мы синхронизируем одно относительно другого.

            Т е зная, что одно событие произошло, мы может точно сказать когда произойдет другое. Мера понятия "точно" - это показание таймера.


  1. Vitalics
    26.04.2023 04:59

    Интересная статья показывающая варианты использования отмены промисов. Единственное замечание - это то что если промис отменить то node.js(предполагаю что в браузере анологичное поведение) удалит промис из call stack и еще и удалит ссылку на выполнение. Это значит, что мы экономим память приложению тк явно показываем GC что данный блок кода уже неактуален


    1. mayorovp
      26.04.2023 04:59

      Не совсем так. Отменённый промис — это просто разновидность завершённого с ошибкой, тип ошибки — DOMException (потому что AbortController — это не часть языка, а часть HTML API). Все эффекты вроде удаления ссылок — это просто следствия перехода промиса в завершённое состояние.


  1. nin-jin
    26.04.2023 04:59
    -2

    const controller = new AbortController();
    
    // После создания экземпляра AbortController, можно получить экземпляр AbortSignal, используя свойство signal:
    
    const signal = controller.signal;
    
    // Имитация отмены запроса через 3 секунды
    
    setTimeout(() => {
      controller.abort();
    }, 3000);
    
    fetch('https://api.example.com/data', { signal })
      .then(response => response.json())
      .then(data => console.log(data))
      .catch(error => {
        if (error.name === 'AbortError') {
          console.log('Fetch request aborted');
        } else {
          console.error('Fetch request failed:', error);
        }
      });

    Лучше всё же как-то так:

    const fiber = new Fiber( ()=> console.log( fetch('https://api.example.com/data').json() ) )
    setTimeout( ()=> fiber.destructor(), 3000 )
    fiber.async()

    Не надо учить детей своим вредным привычкам.


    1. mayorovp
      26.04.2023 04:59
      -1

      Функцию fetch ваша Fiber подменяет? Если нет, то код не эквивалентный. Если да — то не надо учить детей вредным привычкам, стандартные функции должны работать в соответствии со стандартом, а не вашими хотелками.


      1. nin-jin
        26.04.2023 04:59
        -1

        А это и не глобальная функция. Делать выводы, не разобравшись в контексте, тоже дурная привычка.


        1. mayorovp
          26.04.2023 04:59
          -1

          У вас в коде я не вижу никаких импортов.


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


  1. john_samilin
    26.04.2023 04:59

    Примечательно то, что запрос на клиенте мы отменили, а на сервере это никак не отразилось. Он все равно потратит время и ресурсы на то, чтобы обработать запрос до конца.


    1. mayorovp
      26.04.2023 04:59

      От сервера зависит.