Українська

Дізнайтеся, як використовувати AbortController у JavaScript для ефективного скасування асинхронних операцій, таких як 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);
    }
  });

// Щоб скасувати fetch-запит:
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; // Або прокинути помилку для обробки вище
    } else {
      console.error('Fetch error:', error);
      throw error; // Повторно прокинути помилку для обробки вище
    }
  }
}

fetchData();

// Щоб скасувати fetch-запит:
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));

// Щоб скасувати тайм-аут:
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 });

// Щоб скасувати слухач подій:
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(); // Скасувати fetch-запит, коли компонент демонтується
    };
  }, []); // Порожній масив залежностей гарантує, що цей ефект виконається лише один раз при монтуванні

  return (
    
{data ? (

Data: {JSON.stringify(data)}

) : (

Loading...

)}
); } export default MyComponent;

Пояснення:

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

Просунуті випадки використання

Ланцюгування AbortSignal

Іноді вам може знадобитися зв'язати кілька AbortSignal разом. Наприклад, у вас може бути батьківський компонент, якому потрібно скасувати операції у своїх дочірніх компонентах. Ви можете досягти цього, створивши новий AbortController і передавши його сигнал як батьківському, так і дочірнім компонентам.

Використання AbortController зі сторонніми бібліотеками

Якщо ви використовуєте сторонню бібліотеку, яка безпосередньо не підтримує AbortSignal, вам може знадобитися адаптувати свій код для роботи з механізмом скасування бібліотеки. Це може включати обгортання асинхронних функцій бібліотеки у власні функції, які обробляють AbortSignal.

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

Сумісність з браузерами

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

Поліфіли

Для старих браузерів, які не підтримують AbortController нативно, ви можете використовувати поліфіл. Поліфіл - це фрагмент коду, який забезпечує функціональність новішої функції в старих браузерах. В Інтернеті є кілька доступних поліфілів для AbortController.

Висновок

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

Додаткові матеріали для читання