Русский

Узнайте, как использовать JavaScript AbortController для эффективной отмены асинхронных операций, таких как запросы fetch, таймеры и многое другое.

JavaScript AbortController: Освоение отмены асинхронных операций

В современной веб-разработке асинхронные операции являются повсеместными. Получение данных с API, установка таймеров и обработка взаимодействий с пользователем часто включают в себя код, который выполняется независимо и потенциально в течение длительного времени. Однако есть сценарии, когда вам нужно отменить эти операции до их завершения. Именно здесь на помощь приходит интерфейс AbortController в JavaScript. Он предоставляет чистый и эффективный способ отправки сигналов отмены запросов операциям DOM и другим асинхронным задачам.

Понимание необходимости отмены

Прежде чем углубляться в технические детали, давайте поймем, почему отмена асинхронных операций важна. Рассмотрим эти распространенные сценарии:

Введение в AbortController и AbortSignal

Интерфейс AbortController разработан для решения проблемы отмены асинхронных операций. Он состоит из двух основных компонентов:

Основное использование: отмена запросов Fetch

Начнем с простого примера отмены запроса fetch:


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

fetch('https://api.example.com/data', { signal })
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('Data:', data);
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
    } else {
      console.error('Fetch error:', error);
    }
  });

// To cancel the fetch request:
controller.abort();

Пояснение:

  1. Мы создаем экземпляр AbortController.
  2. Мы получаем связанный AbortSignal от controller.
  3. Мы передаем signal параметрам fetch.
  4. Если нам нужно отменить запрос, мы вызываем controller.abort().
  5. В блоке .catch() мы проверяем, является ли ошибка AbortError. Если это так, мы знаем, что запрос был отменен.

Обработка AbortError

Когда вызывается controller.abort(), запрос fetch будет отклонен с AbortError. Крайне важно правильно обрабатывать эту ошибку в вашем коде. Невыполнение этого может привести к необработанным отклонениям промисов и неожиданному поведению.

Вот более надежный пример с обработкой ошибок:


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

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data', { signal });
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }
    const data = await response.json();
    console.log('Data:', data);
    return data;
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
      return null; // Or throw the error to be handled further up
    } else {
      console.error('Fetch error:', error);
      throw error; // Re-throw the error to be handled further up
    }
  }
}

fetchData();

// To cancel the fetch request:
controller.abort();

Рекомендации по обработке AbortError:

Отмена таймеров с помощью AbortSignal

AbortSignal также можно использовать для отмены таймеров, созданных с помощью setTimeout или setInterval. Это требует немного больше ручной работы, так как встроенные функции таймера напрямую не поддерживают AbortSignal. Вам нужно создать пользовательскую функцию, которая прослушивает сигнал отмены и очищает таймер при его срабатывании.


function cancellableTimeout(callback, delay, signal) {
  let timeoutId;

  const timeoutPromise = new Promise((resolve, reject) => {
    timeoutId = setTimeout(() => {
      resolve(callback());
    }, delay);

    signal.addEventListener('abort', () => {
      clearTimeout(timeoutId);
      reject(new Error('Timeout Aborted'));
    });
  });

  return timeoutPromise;
}

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


cancellableTimeout(() => {
  console.log('Timeout executed');
}, 2000, signal)
.then(() => console.log("Timeout finished successfully"))
.catch(err => console.log(err));

// To cancel the timeout:
controller.abort();

Пояснение:

  1. Функция cancellableTimeout принимает обратный вызов, задержку и AbortSignal в качестве аргументов.
  2. Она устанавливает setTimeout и сохраняет идентификатор таймера.
  3. Она добавляет прослушиватель событий к AbortSignal, который прослушивает событие abort.
  4. Когда событие abort срабатывает, прослушиватель событий очищает таймер и отклоняет промис.

Отмена обработчиков событий

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


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

const button = document.getElementById('myButton');

button.addEventListener('click', () => {
  console.log('Button clicked!');
}, { signal });

// To cancel the event listener:
controller.abort();

Пояснение:

  1. Мы передаем signal в качестве опции методу addEventListener.
  2. Когда вызывается controller.abort(), обработчик событий будет автоматически удален.

AbortController в компонентах React

В React вы можете использовать AbortController для отмены асинхронных операций, когда компонент отключается. Это необходимо для предотвращения утечек памяти и ошибок, вызванных обновлением отключенных компонентов. Вот пример использования хука useEffect:


import React, { useState, useEffect } from 'react';

function MyComponent() {
  const [data, setData] = useState(null);

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

    async function fetchData() {
      try {
        const response = await fetch('https://api.example.com/data', { signal });
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        const data = await response.json();
        setData(data);
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('Fetch aborted');
        } else {
          console.error('Fetch error:', error);
        }
      }
    }

    fetchData();

    return () => {
      controller.abort(); // Cancel the fetch request when the component unmounts
    };
  }, []); // Empty dependency array ensures this effect runs only once on mount

  return (
    
{data ? (

Data: {JSON.stringify(data)}

) : (

Loading...

)}
); } export default MyComponent;

Пояснение:

  1. Мы создаем AbortController внутри хука useEffect.
  2. Мы передаем signal запросу fetch.
  3. Мы возвращаем функцию очистки из хука useEffect. Эта функция будет вызвана, когда компонент отключается.
  4. Внутри функции очистки мы вызываем controller.abort(), чтобы отменить запрос fetch.

Расширенные варианты использования

Цепочка AbortSignals

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

Использование AbortController с сторонними библиотеками

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

Преимущества использования AbortController

Совместимость с браузерами

AbortController широко поддерживается в современных браузерах, включая Chrome, Firefox, Safari и Edge. Вы можете проверить таблицу совместимости в MDN Web Docs для получения последней информации.

Polyfills

Для более старых браузеров, которые изначально не поддерживают AbortController, вы можете использовать polyfill. Polyfill — это часть кода, которая предоставляет функциональность новой функции в старых браузерах. В Интернете доступно несколько polyfills AbortController.

Заключение

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

Дополнительное чтение