Sytuacja kobiet w IT w 2024 roku
30.01.20205 min
Wojciech Trawiński
Asseco Poland S.A.

Wojciech TrawińskiProgramista AngularAsseco Poland S.A.

RxJS – 7 dobrych praktyk

Poznaj 7 praktyk, które przydadzą Ci się w pracy z RxJS, czyli biblioteką Angulara.

RxJS – 7 dobrych praktyk

Angular to obecnie jedno z najpopularniejszych narzędzi pozwalających na tworzenie aplikacji internetowych. Jedną z kluczowych składowych tej platformy jest biblioteka RxJS umożliwiająca pisanie reaktywnego kodu (ang. push-based approach). Programista Angular bardzo często ma do czynienia ze strumieniami danych, które obecne są w niemal każdym module tego frameworka.

W początkowym etapie kariery programiści mogą ograniczać się jedynie do subskrypcji do obecnych strumieni, jednak w pewnym momencie pojawia się konieczność poznania biblioteki RxJS w znacznie większym stopniu. W moim artykule przedstawię wybrane 7 dobrych praktyk i porad, które, mam nadzieję, poszerzą Twoją wiedzę na temat tej biblioteki.

Operator first

W pewnych sytuacjach jesteśmy zainteresowani tylko pojedynczą wartością wyemitowaną przez strumień danych. W takim przypadku możemy skorzystać z operatora first lub take(1). Warto jednak pamiętać, że operator first pozwala na przekazanie jako argument funkcji testującej, która zostanie wywołana z wartością pojawiającą się w strumieniu danych. W ten sposób możemy ograniczyć czas trwania subskrypcji do źródła danych do momentu emisji pierwszej wartości spełniającej funkcję testującą:

W powyższym przykładzie, subskrypcja do źródłowego strumienia (emitującego kolejne liczby naturalne w odstępie 1s) zostanie zakończona, gdy wyemitowana zostanie pierwsza wartość, dla której przekazana do operatora first funkcja testująca zwróci wartość prawdziwą (ang. truthy value), czyli w tym przypadku liczba 6.

Operator filter

Operator ten działa analogicznie jak metoda o tej samej nazwie obecna w prototypie tablic w JavaScript. Pozwala ona na filtrowanie wartości trafiających do wynikowego strumienia. Pisząc kod w Angularze, wykorzystujemy TypeScript, będący nadzbiorem języka JavaScript, który m.in pozwala na słabe statyczne typowanie. Jeżeli źródłowy strumień danych emituje obiekty będące instancją jednej z kilku klas, to możemy spodziewać się, że jeżeli odfiltrujemy wartości po ich typie, to w wynikowym strumieniu będziemy mieli dostęp do interfejsu tej konkretnej klasy. Nic bardziej mylnego:

W celu zawężenia typu na podstawie funkcji testującej przekazanej do operatora filter, musimy zadeklarować typ zwracany z tej funkcji jako tzw. type predicate:

Operator distinctUntilChanged

Jeżeli nie chcemy, aby w wynikowym strumieniu pojawiły się kolejno dwie takie same wartości, to możemy skorzystać z operatora distinctUntilChanged. Problem pojawia się w momencie, gdy chcemy porównywać obiekty, ponieważ w JavaScript porównujemy referencje do obiektów. W rezultacie, obiekty mające takie same pola i ich wartości będą uznane są różne od siebie. Z pomocą przychodzi tutaj wersja operatora distinctUntilChanged, do której przekazujemy funkcję testującą, na podstawie której stwierdzamy czy obiekty są sobie równe:

W powyższym przykładzie, strumień incorrectResults$ wyemituje obiekt o id=1 dwukrotnie, podczas gdy wersja z funkcją testującą da pożądany efekt.

Zjawisko wyścigu

Jeżeli pracujesz nad aplikacją, w której na podstawie interakcji użytkownika pobierasz dane odpowiadające aktualnym wartościom filtrów lub innych kontrolek, to zdecydowanie musisz zwrócić uwagę na zjawisko wyścigu. W skrócie, wykonujemy zapytanie http w chwili t=0 oraz następne w dla  t=5s. Nie mamy gwarancji, że odpowiedź z serwera dla późniejszego zapytania pojawi się po odpowiedzi na wcześniejsze zapytanie. W rezultacie, może dojść do sytuacji, gdy będziemy wyświetlać nieaktualne dane dla wcześniejszych preferencji użytkownika. Biblioteka RxJS dostarcza operator switchMap, który pozwala dzięki jednej linijce kodu poradzić sobie ze zjawiskiem wyścigu (ang. race condition):

W związku z tym, że w powyższym przykładzie wartości reprezentujące id użytkownika emitowane są jedna po drugiej, zapytanie dla id=1 zostanie anulowane, dzięki czemu w wynikowym strumieniu pojawi się odpowiedź z serwera dla aktualnego id użytkownika.

Niecierpliwy użytkownik

Zdarzają się sytuacje, gdy zniecierpliwiony użytkownik kilkukrotnie klika w przycisk interfejsu, np. w celu zalogowania się lub odświeżenia danych. W takich przypadkach wykonywanie ponownego zapytania w odpowiedzi na kolejne kliknięcia użytkownika nie ma najmniejszego sensu, co gorsza, zwiększa to czas potrzebny do otrzymania odpowiedzi z serwera. W takiej sytuacji możemy ponownie skorzystać z operatora, który dostarcza biblioteka RxJS, mianowicie exhaustMap:

Operator exhaustMap ignoruje wartości emitowane ze źródłowego strumienia, podczas gdy wewnętrzny strumień zwracany z tego operatora nie został zakończony - w powyższym przykładzie zakończenie wewnętrznego strumienia to moment, gdy otrzymamy odpowiedź z serwera. Reasumując, jeżeli zapytanie do serwera jest w trakcie (status pending), to emisja wartości ze źródłowego strumienia nie będzie skutkowała wykonaniem kolejnego zapytania http.

Zarządzanie subskrypcjami

Jeżeli posiadamy w naszej aplikacji miejsca, w których subskrybujemy się do źródeł danych, to należy pamiętać o anulowaniu takiej subskrypcji, tak aby nie tworzyć wycieków pamięci (ang. memory leaks). Zarządzanie subskrypcjami staje się problematyczne, gdy rośnie ich liczba:

Powyższe rozwiązanie zdecydowanie nie jest skalowalne, a liczba linijek kodu sugeruje, że musi istnieć lepsze rozwiązanie. Instancja klasy Subscription posiada metodę add, pozwalającą na dodanie do istniejącej subskrypcji kolejnych, które zostaną anulowane w momencie, gdy na początkowej instancji wywołamy metodę unsubscribe:

Dodatkowo nie ma już konieczności sprawdzania, czy poszczególne subskrypcje są zdefiniowane przed wywołaniem na nich metody unsubscribe.

Multicasting

W RxJS wyróżniamy tzw. hot & cold observables. Strumienie danych reprezentujące zapytanie http to strumienie typu cold, w związku z tym producent wartości jest tworzony w momencie subskrypcji do strumienia. W rezultacie, jeżeli taki strumień posłuży za źródłowy dla kilku wynikowych strumieni danych, to niepotrzebnie wykonamy kilka takich samych zapytań http:

W powyższym przykładzie strumień reprezentujący zapytanie http służy jako źródłowy strumień dla dwóch wynikowych, którymi emitowane są kolejno imię i email użytkownika. Jeżeli spojrzymy w konsolę, to zobaczymy, że wykonujemy dwa zapytania http po ten sam zasób. Nie jest to pożądane, dlatego warto wykorzystać operator shareReplay, który pozwala na tzw. multicasting, czyli zamianą strumienia cold na hot:

Dzięki użyciu tego operatora zachowaliśmy wcześniejsze zachowanie i uniknęliśmy wykonywania niepotrzebnych zapytań do serwera po ten sam zasób.

Podsumowanie

Biblioteka RxJS to potężne narzędzie, które na początku może przytłaczać złożonością i liczbą dostarczanych operatorów. Z mojego doświadczenia wynika, że warto poświęcić czas na poznanie tej biblioteki, ponieważ pracując jako programista Angular mamy do czynienia z reaktywnym kodem niemalże w każdym miejscu aplikacji. Wykorzystanie biblioteki RxJS bardzo często pozwala w szybki i łatwy sposób rozwiązać złożone problemy, z którymi coraz częściej musi mierzyć się programista.

<p>Loading...</p>