Dowiedz się, jak używać AbortController w JavaScript, aby skutecznie anulować operacje asynchroniczne, takie jak żądania pobierania, timery i inne, zapewniając czystszy i wydajniejszy kod.
JavaScript AbortController: Opanowanie Anulowania Operacji Asynchronicznych
We współczesnym tworzeniu stron internetowych operacje asynchroniczne są wszechobecne. Pobieranie danych z interfejsów API, ustawianie timerów i obsługa interakcji użytkownika często obejmują kod, który działa niezależnie i potencjalnie przez dłuższy czas. Istnieją jednak scenariusze, w których należy anulować te operacje przed ich zakończeniem. Właśnie tutaj interfejs AbortController
w JavaScript przychodzi z pomocą. Zapewnia czysty i wydajny sposób sygnalizowania żądań anulowania do operacji DOM i innych zadań asynchronicznych.
Zrozumienie potrzeby anulowania
Zanim zagłębimy się w szczegóły techniczne, zrozumiejmy, dlaczego anulowanie operacji asynchronicznych jest ważne. Rozważmy te typowe scenariusze:
- Nawigacja użytkownika: Użytkownik inicjuje zapytanie wyszukiwania, uruchamiając żądanie API. Jeśli szybko przejdzie do innej strony, zanim żądanie się zakończy, oryginalne żądanie staje się nieistotne i powinno zostać anulowane, aby uniknąć niepotrzebnego ruchu w sieci i potencjalnych skutków ubocznych.
- Zarządzanie limitem czasu: Ustawiasz limit czasu dla operacji asynchronicznej. Jeśli operacja zakończy się przed upływem limitu czasu, należy anulować limit czasu, aby zapobiec zbędnemu wykonywaniu kodu.
- Odmontowywanie komponentów: W frameworkach front-endowych, takich jak React lub Vue.js, komponenty często wykonują żądania asynchroniczne. Gdy komponent się odmontowuje, wszelkie trwające żądania związane z tym komponentem powinny zostać anulowane, aby uniknąć wycieków pamięci i błędów spowodowanych aktualizacją odmontowanych komponentów.
- Ograniczenia zasobów: W środowiskach o ograniczonych zasobach (np. urządzenia mobilne, systemy wbudowane) anulowanie niepotrzebnych operacji może zwolnić cenne zasoby i poprawić wydajność. Na przykład anulowanie pobierania dużego obrazu, jeśli użytkownik przewinie obok tej sekcji strony.
Wprowadzenie do AbortController i AbortSignal
Interfejs AbortController
został zaprojektowany w celu rozwiązania problemu anulowania operacji asynchronicznych. Składa się z dwóch kluczowych komponentów:
- AbortController: Ten obiekt zarządza sygnałem anulowania. Ma jedną metodę,
abort()
, która służy do sygnalizowania żądania anulowania. - AbortSignal: Ten obiekt reprezentuje sygnał, że operacja powinna zostać przerwana. Jest powiązany z
AbortController
i jest przekazywany do operacji asynchronicznej, która musi być anulowana.
Podstawowe użycie: Anulowanie żądań pobierania
Zacznijmy od prostego przykładu anulowania żądania 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();
Wyjaśnienie:
- Tworzymy instancję
AbortController
. - Uzyskujemy powiązany
AbortSignal
zcontroller
. - Przekazujemy
signal
do opcjifetch
. - Jeśli musimy anulować żądanie, wywołujemy
controller.abort()
. - W bloku
.catch()
sprawdzamy, czy błąd toAbortError
. Jeśli tak, wiemy, że żądanie zostało anulowane.
Obsługa AbortError
Po wywołaniu controller.abort()
, żądanie fetch
zostanie odrzucone z AbortError
. Ważne jest, aby odpowiednio obsłużyć ten błąd w swoim kodzie. Niezastosowanie się do tego może prowadzić do nieobsłużonych odrzuczeń obietnic i nieoczekiwanego zachowania.
Oto bardziej solidny przykład z obsługą błędów:
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();
Najlepsze praktyki dotyczące obsługi AbortError:
- Sprawdź nazwę błędu: Zawsze sprawdzaj, czy
error.name === 'AbortError'
, aby upewnić się, że obsługujesz właściwy typ błędu. - Zwróć wartość domyślną lub ponownie rzuć: W zależności od logiki aplikacji możesz chcieć zwrócić wartość domyślną (np.
null
) lub ponownie rzucić błąd, aby obsłużyć go wyżej w stosie wywołań. - Wyczyść zasoby: Jeśli operacja asynchroniczna przydzieliła jakiekolwiek zasoby (np. timery, listenery zdarzeń), wyczyść je w obsłudze
AbortError
.
Anulowanie timerów z AbortSignal
AbortSignal
może być również używany do anulowania timerów utworzonych za pomocą setTimeout
lub setInterval
. Wymaga to nieco więcej pracy ręcznej, ponieważ wbudowane funkcje timera nie obsługują bezpośrednio AbortSignal
. Musisz utworzyć funkcję niestandardową, która nasłuchuje sygnału przerwania i czyści timer, gdy zostanie uruchomiony.
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();
Wyjaśnienie:
- Funkcja
cancellableTimeout
przyjmuje wywołanie zwrotne, opóźnienie iAbortSignal
jako argumenty. - Ustawia
setTimeout
i przechowuje identyfikator limitu czasu. - Dodaje do
AbortSignal
detektor zdarzeń, który nasłuchuje zdarzeniaabort
. - Gdy zdarzenie
abort
zostanie uruchomione, detektor zdarzeń czyści limit czasu i odrzuca obietnicę.
Anulowanie detektorów zdarzeń
Podobnie jak w przypadku timerów, możesz użyć AbortSignal
do anulowania detektorów zdarzeń. Jest to szczególnie przydatne, gdy chcesz usunąć detektory zdarzeń powiązane z komponentem, który jest odmontowywany.
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();
Wyjaśnienie:
- Przekazujemy
signal
jako opcję do metodyaddEventListener
. - Gdy
controller.abort()
zostanie wywołane, detektor zdarzeń zostanie automatycznie usunięty.
AbortController w komponentach React
W React możesz użyć AbortController
do anulowania operacji asynchronicznych, gdy komponent się odmontowuje. Jest to niezbędne, aby zapobiec wyciekom pamięci i błędom spowodowanym aktualizacją odmontowanych komponentów. Oto przykład używający haka 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;
Wyjaśnienie:
- Tworzymy
AbortController
wewnątrz hakauseEffect
. - Przekazujemy
signal
do żądaniafetch
. - Zwracamy funkcję czyszczącą z haka
useEffect
. Ta funkcja zostanie wywołana, gdy komponent się odmontowuje. - Wewnątrz funkcji czyszczącej wywołujemy
controller.abort()
, aby anulować żądanie pobierania.
Zaawansowane przypadki użycia
Łańcuchowanie AbortSignal
Czasami możesz chcieć połączyć wiele AbortSignal
razem. Na przykład możesz mieć komponent nadrzędny, który musi anulować operacje w swoich komponentach podrzędnych. Można to osiągnąć, tworząc nowy AbortController
i przekazując jego sygnał zarówno do komponentów nadrzędnych, jak i podrzędnych.
Używanie AbortController z bibliotekami innych firm
Jeśli używasz biblioteki innej firmy, która nie obsługuje bezpośrednio AbortSignal
, może być konieczne dostosowanie kodu do współpracy z mechanizmem anulowania biblioteki. Może to obejmować zawijanie asynchronicznych funkcji biblioteki we własne funkcje, które obsługują AbortSignal
.
Korzyści z używania AbortController
- Poprawa wydajności: Anulowanie niepotrzebnych operacji może zmniejszyć ruch w sieci, wykorzystanie procesora i zużycie pamięci, co prowadzi do poprawy wydajności, zwłaszcza na urządzeniach o ograniczonych zasobach.
- Czystszy kod:
AbortController
zapewnia znormalizowany i elegancki sposób zarządzania anulowaniem, dzięki czemu kod jest bardziej czytelny i łatwiejszy w utrzymaniu. - Zapobieganie wyciekom pamięci: Anulowanie operacji asynchronicznych powiązanych z odmontowanymi komponentami zapobiega wyciekom pamięci i błędom spowodowanym aktualizacją odmontowanych komponentów.
- Lepsze wrażenia użytkownika: Anulowanie nieistotnych żądań może poprawić wrażenia użytkownika, zapobiegając wyświetlaniu nieaktualnych informacji i zmniejszając postrzegane opóźnienia.
Zgodność przeglądarki
AbortController
jest szeroko obsługiwany we współczesnych przeglądarkach, w tym Chrome, Firefox, Safari i Edge. Możesz sprawdzić tabelę zgodności w dokumentach MDN Web Docs, aby uzyskać najnowsze informacje.
Polyfills
W przypadku starszych przeglądarek, które natywnie nie obsługują AbortController
, można użyć polyfill. Polyfill to fragment kodu, który zapewnia funkcjonalność nowszej funkcji w starszych przeglądarkach. Istnieje kilka dostępnych online polyfillów AbortController
.
Podsumowanie
Interfejs AbortController
jest potężnym narzędziem do zarządzania operacjami asynchronicznymi w JavaScript. Używając AbortController
, możesz pisać czystszy, wydajniejszy i bardziej niezawodny kod, który elegancko obsługuje anulowanie. Niezależnie od tego, czy pobierasz dane z interfejsów API, ustawiasz timery, czy zarządzasz detektorami zdarzeń, AbortController
może pomóc w poprawie ogólnej jakości aplikacji internetowych.