Ta strona używa cookies
sprawdź politykę prywatności

Rozumiem

Abort Controller – Anulowanie zapytań asynchronicznych w React’cie

21/1/2020
Bartek Cis

Czym jest Abort Controller i jak go użyć w React’cie do anulowania zapytań asynchronicznych?

Czego się dzisiaj dowiesz?

Na początku opiszę jak działają zapytania asynchroniczne, czym jest Abort Controller w JavaScript i po co go używać w aplikacjach webowych.

Potem opiszę najpierw prosty a potem bardziej złożony sposób na anulowanie sygnałów asynchronicznych w React’cie. Bo do tego w zasadzie przydaje się Abort Controller 🙂

Jeśli nie chcesz czytać całości po prostu kliknij w jeden z punktów poniżej:

  1. Sygnał asynchroniczny i Abort Controller
  2. Cykl życia komponentu w React i dlaczego warto po sobie sprzątać?
  3. Anulowanie sygnału wywołanego podczas ładowania komponentu.
  4. Anulowanie sygnałów wywołanych interakcjami użytkownika.
  5. Wnioski i repozytorium

Sygnał asynchroniczny i Abort Controller

Jak pewnie wiesz JavaScript obsługuje pewien specyficzny sposób programowania- programowanie asynchroniczne. Jeśli tego jeszcze nie wiesz to wróć do tego artykułu za jakiś czas 🙂 

Przy programowaniu asynchronicznym używa się między innymi składni m.in. async – await lub then i służą one głównie do komunikacji z serwerem udostępniającym nam rozmaite dane.

Obsługa zapytania asynchronicznego

Spróbuję trochę wytłumaczyć zasadę działania egzekucji żądań asynchronicznych w JavaScript’cie (tak orientacyjnie oczywiście) żeby to wszystko miało większy sens.

JavaScript jest językiem jednowątkowym czyli wykonuje jedną operację na raz w obrębie pojedynczej sekwencji zdarzeń. Podczas fazy egzekucji kodu poszczególne funkcje umieszczane są w kolejce w której wykonywana jest jedna funkcja na raz. Po jej zakończeniu rozpoczyna się egzekucja kolejnej funkcji. Taka kolejka nazywa się callstack a funkcje są wykonywane synchronicznie.

Funkcje asynchroniczne – zasadza działania

Problem jest kiedy na horyzoncie pojawia się coś asynchronicznego bo zapytanie do serwera może potrwać długo. Jeżeli byłoby traktowane jako funkcja synchroniczna to aplikacja byłaby zablokowana (bo nic innego nie mogłoby się wykonać) do momentu w którym nie dostaliśmy odpowiedzi z serwera.

Aby zapobiec takiej sytuacji Web API posiada metody/interfejsy do obsługi zdarzeń asynchronicznych i w takim przypadku przeglądarka przejmuje kontrolę nad obsługą zapytań do serwera. Odblokowuje wtedy callstack i czeka aż serwer zwróci odpowiedź. Ta odpowiedź przekazywana jest z powrotem do aplikacji jako funkcja zwrotnacallback.

Fetch API

Jednym z interfejsów używanych do komunikacji z serwerami jest Fetch API. Posiada on conajmniej jedną niewątpliwą zaletę – może korzystać z AbortController’a. Wykorzystując np. metodę fetch w swojej aplikacji informujesz przeglądarkę, że ma użyć Fetch API do obsługi tego zdarzenia.

AbortController

AbortController to specjalny obiekt/interfejs posiadający właściwość signal . Tą właściwość można dołączyć do zapytania asynchronicznego z użyciem fetch jako jedną z opcji.

Taka operacja pozwala nam powiązać konkretne zapytanie do serwera z odpowiednim sygnałem. A po co to?

Abort Controller posiada też metodę abort() która wywołana anuluję obsługę tego sygnału. Znaczy to tyle, że jeśli serwer zwróci jakąś odpowiedź to wtedy przeglądarka wie, że ten sygnał nie jest już obsługiwany więc nie przekaże funkcji zwrotnej callback z powrotem do naszej kolejki – callstack.

No tak ale jak to się ma do naszych aplikacji?

Cykl życia komponentu w React i dlaczego warto po sobie sprzątać?

Wszystkie (?) współczesne frameworki JavaScript są oparte o komponenty. Komponenty są to re-używalne elementy które można użyć w dowolnym miejscu aplikacji (oczywiście jeśli są odpowiednio napisane). Można więc powiedzieć, że współczesne aplikacje internetowe mają budowę modularną.

Każdy komponent posiada swój cykl życia czyli moment w którym jest renderowany w aplikacji, moment w którym znika z ekranu użytkownika i wszystko co się z nim dzieje pomiędzy.

Cykle życia w React

Dotychczas kontrolę nad tym co się działo z komponentami (klasowymi) w React’cie można było uzyskać dzięki m.in. takim metodom jak componentDidMount() czy componentWillUnmount() których nazwy same w sobie dość dobrze wyjaśniają przeznaczenie.

Od kiedy do Reacta weszły hooki możemy kontrolować cykl życia komponentu za pomocą useEffect który w zależność od konfiguracji może służyć jako detektor momentu w którym komponent się załadował, uaktualnił swój stan lub ma się dezaktywować.

Dlaczego warto po sobie sprzątać?

Operacje wykonywane wewnątrz komponentu które są asynchroniczne lub działają z opóźnieniem a próbują modyfikować stan komponentu wprowadzają pewne ryzyko.

W naszym przykładzie z sygnałem callback który wrócił do naszego callstack’a może wykonać próbę zmiany stanu. Ale co jeśli komponentu już nie ma bo użytkownik już np. przeszedł do kolejnej strony i zmienił widok?

Wtedy możesz dostać komunikat o ‘wycieku pamięci’ w aplikacji więc potencjalny spadek wydajności naszej aplikacji. Raczej tego nie chcesz. Ten artykuł fajnie opisuje to zagadnienie.

Aby temu zapobiec trzeba anulować wszystkie subskrypcje i sygnały asynchroniczne zanim komponent zostanie zdezaktywowany.

Anulowanie sygnału wywołanego podczas ładowania komponentu

No to taka wstępna teoria już za nami. Teraz rozważymy prosty przypadek gdzie chcesz pobrać dane z API po załadowaniu komponentu:

Na pierwszy rzut oka ten komponent nie wydaje się prosty ale to chyba jeden z najłatwiejszych sposobów na użycie AbortControllera 🙂 Przed potencjalną de-aktywacją komponentu wywołuję metodę abortController.abort()

Anulowanie sygnałów wywołanych interakcjami użytkownika

Tak czysto hipotetycznie, a co jeśli użytkownik będzie chciał pobrać jakieś zasoby po kliknięciu w przycisk (lub dowolna inna interakcja z interfejsem)? Jednak internet zamula i gościu sobie zmienia widok na inny przez co komponent nie jest już aktywny. Jak anulować taki sygnał?

Problem polega na tym, że w jakiś sposób należy przekazać unikalny sygnał do wewnątrz useEffect . Ten sygnał jednak jest wywoływany przez event spoza useEffect czyli onClick . Nie wiem czy w tym momencie już widzisz gdzie jest haczyk 🙂

O ile mi wiadomo nie istnieje gotowy mechanizm w React’cie obsługujący taki scenariusz dlatego szukając inspiracji w internecie wymyśliłem coś takiego 😀 

Fetch z API jako Custom Hook

Na początku zdefiniuje pobieranie danych jako hook w który z założenia będzie dostarczał mi unikalny sygnał z którego mogę skorzystać do anulowania requestów:

A więc w ten sposób mogę dowiązać do zapytania asynchronicznego dowolny sygnał lub utworzyć nowy domyślny który potem łatwo skonsumować 🙂

React Hooks

Teraz trzy oddzielne hooki:

Zadanie pierwszego jest proste – podać do Hooka z API za każdym razem inny argument aby pobierać różne artykuły

Drugi hook to ten co stworzyłem wcześniej. Oprócz samej funkcji wywołującej zapytanie do API ładuje sobie unikalną metodę do abortowania sygnału.

Trzeci hook to tablica w której będę przechowywał wszystkie metody do abortowania. Używam useRef aby przez całą długość życia komponentu mieć aktualne wartości bez potrzeby dodatkowych manewrów 🙂

Click Handler

Krok numer trzy to napisanie funkcji asynchronicznej która będzie obsługiwać nasz ‘klik’. Wygląda tak:

Pierwszą rzeczą którą robię to podanie aktualnej metody abort do tablicy useRef przy użyciu unshift(). Potem wykonuje standardowe operacje czyli pobranie danych i uaktualnienie stanu. W sumie tyle komentarza tu wystarczy 🙂

Uaktualnienie funkcji onDestroy

Ostatnim już krokiem będzie wywołanie wszystkich funkcji wewnątrz naszego useRef w momencie kiedy komponent kończy swój żywot:

Jest to generalnie uzupełniony przykład z podstawowego typu abortu. Tutaj po prostu dodaję funkcję która kolejno wywołuje wszystkie aborty z tablicy a następnie ją wywołuję przy końcu cyklu życia komponentu 🙂 

Co o tym sądzić?

No właśnie… dobre pytanie 🙂 Generalnie ta implementacja jest bardzo uproszczona ale chodzi tylko o to, aby przekazać koncepcję.

Na pewno minusem jest to, że na końcu próbuje abortować wszystkie sygnały nie tylko te które na pewno są aktywne. To coś do poprawienia.

Kolejną rzeczą jest trudność w realnym przetestowaniu tego rozwiązania. Teoretycznie wszystko powinno działać ale jak mieć na 100% pewność 🙂 ?

Nie wiem, może cały ten wysiłek nie był bez sensu bo gdzieś jest gotowe rozwiązanie tego problemu? Biblioteka która załatwia sprawę? Ale czy na pewno chcę jej używać?

Ten cały problem można potraktować jako marginalny i w ogóle go olać?

Hehe no sam nie wiem 🙂 Jakie Ci jeszcze myśli przychodzą do głowy?

Co więcej?

Znowu no właśnie… Generalnie uważam ten temat za dość ciekawy. Jeśli chcesz sprawdzić cały kod z tego artykułu to sprawdź to repo.

Poza tym trzy artykuły które w jakiś sposób mnie inspirowały:

https://www.robinwieruch.de/react-warning-cant-call-setstate-on-an-unmounted-component

https://dom.spec.whatwg.org/#interface-abortcontroller

https://sebastienlorber.com/handling-api-request-race-conditions-in-react

Jeśli też uważasz, że ten artykuł jest pełny none sens’ów to napisz w komentarzu. Może rzeczywiście walnąłem jakieś gafy i pasuje poprawić.

Podsumowanie

Dzisiaj opisałem na czym polega działanie kodu asynchronicznego w JavaScript i czym jest callstack.

Wiesz już też w jaki sposób można anulować sygnały asynchroniczne przy pomocy abortController’a które zostały wysłane ale jeszcze nie zwróciły odpowiedzi.

W końcu zobaczyłeś/aś przykładowe użycia abortController’a w React przy użyciu hooków 🙂

Cześć jestem Bartek.

Na tym blogu wprowadzę Cię w tajniki Front-Endu i programowania webowego.

Warto
Social media & sharing icons powered by UltimatelySocial