Дізнайтеся, як використовувати AbortController у JavaScript для ефективного скасування асинхронних операцій, таких як fetch-запити, таймери тощо, для чистішого та продуктивнішого коду.
JavaScript AbortController: Майстерне скасування асинхронних операцій
У сучасній веб-розробці асинхронні операції є повсюдними. Отримання даних з API, встановлення таймерів та обробка взаємодії з користувачем часто включають код, який виконується незалежно і потенційно протягом тривалого часу. Однак існують сценарії, коли потрібно скасувати ці операції до їх завершення. Саме тут на допомогу приходить інтерфейс AbortController
у JavaScript. Він надає чистий та ефективний спосіб надсилання сигналів про скасування до операцій DOM та інших асинхронних завдань.
Розуміння потреби у скасуванні
Перш ніж занурюватися в технічні деталі, давайте розберемося, чому скасування асинхронних операцій є важливим. Розглянемо ці поширені сценарії:
- Навігація користувача: Користувач ініціює пошуковий запит, що викликає запит до API. Якщо він швидко переходить на іншу сторінку до завершення запиту, початковий запит стає неактуальним і його слід скасувати, щоб уникнути непотрібного мережевого трафіку та потенційних побічних ефектів.
- Керування тайм-аутами: Ви встановлюєте тайм-аут для асинхронної операції. Якщо операція завершується до закінчення тайм-ауту, ви повинні скасувати тайм-аут, щоб запобігти зайвому виконанню коду.
- Демонтаж компонента: У front-end фреймворках, таких як React або Vue.js, компоненти часто роблять асинхронні запити. Коли компонент демонтується, будь-які поточні запити, пов'язані з цим компонентом, слід скасувати, щоб уникнути витоків пам'яті та помилок, викликаних оновленням демонтованих компонентів.
- Обмеження ресурсів: В середовищах з обмеженими ресурсами (наприклад, мобільні пристрої, вбудовані системи) скасування непотрібних операцій може звільнити цінні ресурси та покращити продуктивність. Наприклад, скасування завантаження великого зображення, якщо користувач прокрутив повз цей розділ сторінки.
Знайомство з AbortController та AbortSignal
Інтерфейс AbortController
призначений для вирішення проблеми скасування асинхронних операцій. Він складається з двох ключових компонентів:
- AbortController: Цей об'єкт керує сигналом скасування. Він має єдиний метод
abort()
, який використовується для сигналізації про запит на скасування. - 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();
Пояснення:
- Ми створюємо екземпляр
AbortController
. - Ми отримуємо пов'язаний
AbortSignal
зcontroller
. - Ми передаємо
signal
в опціїfetch
. - Якщо нам потрібно скасувати запит, ми викликаємо
controller.abort()
. - У блоці
.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:
- Перевіряйте назву помилки: Завжди перевіряйте, чи
error.name === 'AbortError'
, щоб переконатися, що ви обробляєте правильний тип помилки. - Повертайте значення за замовчуванням або прокидайте помилку: Залежно від логіки вашої програми, ви можете повернути значення за замовчуванням (наприклад,
null
) або повторно прокинути помилку для обробки вище по стеку викликів. - Очищуйте ресурси: Якщо асинхронна операція виділила будь-які ресурси (наприклад, таймери, слухачі подій), очистіть їх в обробнику
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();
Пояснення:
- Функція
cancellableTimeout
приймає колбек, затримку таAbortSignal
як аргументи. - Вона встановлює
setTimeout
і зберігає ідентифікатор тайм-ауту. - Вона додає слухач подій до
AbortSignal
, який слухає подіюabort
. - Коли подія
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();
Пояснення:
- Ми передаємо
signal
як опцію до методуaddEventListener
. - Коли викликається
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;
Пояснення:
- Ми створюємо
AbortController
всередині хукаuseEffect
. - Ми передаємо
signal
доfetch
-запиту. - Ми повертаємо функцію очищення з хука
useEffect
. Ця функція буде викликана, коли компонент демонтується. - Всередині функції очищення ми викликаємо
controller.abort()
, щоб скасувати fetch-запит.
Просунуті випадки використання
Ланцюгування AbortSignal
Іноді вам може знадобитися зв'язати кілька AbortSignal
разом. Наприклад, у вас може бути батьківський компонент, якому потрібно скасувати операції у своїх дочірніх компонентах. Ви можете досягти цього, створивши новий AbortController
і передавши його сигнал як батьківському, так і дочірнім компонентам.
Використання AbortController зі сторонніми бібліотеками
Якщо ви використовуєте сторонню бібліотеку, яка безпосередньо не підтримує AbortSignal
, вам може знадобитися адаптувати свій код для роботи з механізмом скасування бібліотеки. Це може включати обгортання асинхронних функцій бібліотеки у власні функції, які обробляють AbortSignal
.
Переваги використання AbortController
- Покращена продуктивність: Скасування непотрібних операцій може зменшити мережевий трафік, використання ЦП та споживання пам'яті, що призводить до покращення продуктивності, особливо на пристроях з обмеженими ресурсами.
- Чистіший код:
AbortController
надає стандартизований та елегантний спосіб керування скасуванням, роблячи ваш код більш читабельним та легким для підтримки. - Запобігання витокам пам'яті: Скасування асинхронних операцій, пов'язаних з демонтованими компонентами, запобігає витокам пам'яті та помилкам, викликаним оновленням демонтованих компонентів.
- Кращий користувацький досвід: Скасування неактуальних запитів може покращити користувацький досвід, запобігаючи відображенню застарілої інформації та зменшуючи відчутну затримку.
Сумісність з браузерами
AbortController
широко підтримується в сучасних браузерах, включаючи Chrome, Firefox, Safari та Edge. Ви можете перевірити таблицю сумісності на MDN Web Docs для отримання найсвіжішої інформації.
Поліфіли
Для старих браузерів, які не підтримують AbortController
нативно, ви можете використовувати поліфіл. Поліфіл - це фрагмент коду, який забезпечує функціональність новішої функції в старих браузерах. В Інтернеті є кілька доступних поліфілів для AbortController
.
Висновок
Інтерфейс AbortController
- це потужний інструмент для керування асинхронними операціями в JavaScript. Використовуючи AbortController
, ви можете писати чистіший, більш продуктивний та надійний код, який витончено обробляє скасування. Незалежно від того, чи отримуєте ви дані з API, встановлюєте таймери або керуєте слухачами подій, AbortController
може допомогти вам покращити загальну якість ваших веб-додатків.