Observables – co to jest? I jak używać ich w kodzie

Łukasz Flak

Wprowadzenie

Observables są istotną częścią frameworka Angular, która zapewnia potężny sposób obsługi asynchronicznych strumieni danych. W tym artykule zbadamy, czym są obserwowalne, dlaczego są ważne i jak można je wykorzystać w kodzie Angular. Przedstawimy również przykłady kodu, aby zilustrować ich użycie i zademonstrować ich zalety.

Zrozumienie Observables

Observables są reprezentacją strumienia danych, które mogą zmieniać się w czasie. Są one podstawową częścią biblioteki Reactive Extensions for JavaScript (RxJS), która jest szeroko stosowana w aplikacjach Angular. Observables mogą emitować wiele wartości w czasie i mogą być obserwowane przez wielu subskrybentów. Podążają za wzorcem projektowym obserwatora, w którym Observable jest producentem wartości, a obserwatorzy (subskrybenci) reagują na te wartości.

Tworzenie Observables

Aby utworzyć observable, można użyć klasy Observable dostarczanej przez RxJS. Oto przykład tworzenia prostego observable, który emituje sekwencję liczb:

import { Observable } from 'rxjs';

const numberObservable = new Observable<number>((observer) => {
  let count = 0;

  const intervalId = setInterval(() => {
    observer.next(count++);
  }, 1000);

  return () => {
    clearInterval(intervalId);
  };
});

W tym przykładzie tworzymy observable, który emituje liczbę co sekundę. Konstruktor observable przyjmuje funkcję, która definiuje sposób, w jaki observable będzie generował wartości. W tym przypadku funkcja otrzymuje obiekt observer i używa metody next do emitowania wartości.

Subskrybowanie Observables

Po utworzeniu observable można go zasubskrybować, aby zacząć otrzymywać emitowane przez niego wartości. Oto jak można zasubskrybować utworzony wcześniej obiekt numberObservable:

numberObservable.subscribe((value) => {
  console.log(value);
});

W tym przykładzie subskrybujemy numberObservable i udostępniamy funkcję zwrotną do obsługi emitowanych wartości. Za każdym razem, gdy emitowana jest nowa wartość, wywoływana jest funkcja zwrotna z tą wartością.

Operators i Transformation

Observables zapewniają szeroki zakres operatorów, które umożliwiają przekształcanie, filtrowanie, łączenie i manipulowanie emitowanymi wartościami. Przyjrzyjmy się przykładowi wykorzystującemu operator map do przekształcenia emitowanych liczb w ich kwadraty:

import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';

const squaredObservable = numberObservable.pipe(
  map((value) => value * value)
);

squaredObservable.subscribe((value) => {
  console.log(value);
});

W tym przykładzie użyliśmy metody pipe do połączenia operatora map z numberObservable. Operator map przekształca każdą emitowaną wartość, mnożąc ją przez siebie. W rezultacie otrzymujemy wartości podniesione do kwadratu.

Filtrowanie Observables

Oprócz transformacji, observables umożliwiają filtrowanie emitowanych wartości na podstawie określonych warunków. Operator filtrowania jest powszechnie używany do tego celu. Rozważmy przykład, w którym odfiltrowujemy liczby parzyste z numberObservable:

import { Observable } from 'rxjs';
import { filter } from 'rxjs/operators';

const oddNumberObservable = numberObservable.pipe(
  filter((value) => value % 2 !== 0)
);

oddNumberObservable.subscribe((value) => {
  console.log(value);
});

W tym przykładzie użyliśmy operatora filtru, aby zezwolić tylko liczbom nieparzystym na przejście przez observable. Funkcja wywołania zwrotnego wewnątrz operatora filtru sprawdza, czy wartość nie jest podzielna przez 2 (tj. liczba nieparzysta) i emituje tylko takie wartości.

Obsługa błędów

Observables zapewniają również mechanizmy obsługi błędów emitowanych podczas strumienia. Operator catchError jest powszechnie używany do wychwytywania błędów i ich obsługi. Rozważmy przykład, w którym błąd jest celowo wyrzucany po spełnieniu określonego warunku:

import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

const errorObservable = new Observable<number>((observer) => {
  let count = 0;

  const intervalId = setInterval(() => {
    if (count === 3) {
      observer.error('Something went wrong!');
    } else {
      observer.next(count++);
    }
  }, 1000);

  return () => {
    clearInterval(intervalId);
  };
});

errorObservable.pipe(
  catchError((error) => {
    console.log('Error:', error);
    return throwError('Error occurred');
  })
).subscribe({
  next: (value) => {
    console.log(value);
  },
  error: (error) => {
    console.log('Handled Error:', error);
  },
});

W tym przykładzie celowo rzuciliśmy błąd, gdy liczba osiągnie 3. Operator catchError wychwytuje błąd, rejestruje go i emituje nowy błąd za pomocą throwError. Subskrybent zapewnia oddzielne procedury obsługi „next” i „error” do obsługi odpowiednio emitowanych wartości i błędów.

Rezygnacja z subskrypcji i czyszczenie

Observables pozwalają na łatwe czyszczenie, zapewniając sposób na wypisanie się ze strumienia. Gdy subskrybujesz observables, metoda subskrypcji zwraca obiekt subskrypcji, który może być użyty do anulowania subskrypcji. Oto przykład:

const subscription = numberObservable.subscribe((value) => {
  console.log(value);
});

// Unsubscribe after 5 seconds
setTimeout(() => {
  subscription.unsubscribe();
}, 5000);

W tym przykładzie przechowujemy obiekt subskrypcji w zmiennej, a następnie wywołujemy jego metodę anulowania subskrypcji po 5 sekundach. Gwarantuje to, że przestaniemy otrzymywać wartości z observable i wyczyścimy wszelkie zasoby z nim powiązane.

Testowanie Observables

Właściwym podejściem do testowania observables jest użycie funkcji fakeAsync dostarczanej przez pakiet @angular/core/testing. Ta funkcja narzędziowa umożliwia pisanie synchronicznie wyglądających testów dla kodu asynchronicznego, w tym observables.

Aby zademonstrować, jak używać fakeAsync do testowania observables, rozważmy prosty przykład, w którym mamy usługę zwracającą observable. Chcemy sprawdzić, czy observable emituje oczekiwane wartości.

Po pierwsze, załóżmy, że mamy usługę o nazwie DataService, która udostępnia observable:

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';

@Injectable()
export class DataService {
  getData(): Observable<number> {
    return of(1, 2, 3, 4, 5);
  }
}

Teraz napiszmy test używający fakeAsync, aby sprawdzić, czy observable emituje oczekiwane wartości:

import { fakeAsync, tick } from '@angular/core/testing';
import { DataService } from './data.service';

describe('DataService', () => {
  let service: DataService;

  beforeEach(() => {
    service = new DataService();
  });

  it('should emit values from the observable', fakeAsync(() => {
    let result: number[] = [];

    service.getData().subscribe((value: number) => {
      result.push(value);
    });

    tick(); // Simulate the passage of time

    expect(result).toEqual([1, 2, 3, 4, 5]);
  }));
});

W powyższym teście użyliśmy funkcji fakeAsync do napisania naszego kodu testowego. W bloku fakeAsync zasubskrybowaliśmy observable zwracane przez getData() i zapisaliśmy emitowane wartości w tablicy wyników. Następnie użyliśmy funkcji tick, aby opróżnić wszystkie oczekujące mikrozadania i przyspieszyć wirtualny zegar. Po funkcji tick możemy wykonać asercje na tablicy wyników, aby upewnić się, że observable wyemitował oczekiwane wartości. Używając fakeAsync, możemy pisać czyste i synchronicznie wyglądające testy dla asynchronicznych observables. Zapewnia to wygodny sposób kontrolowania czasu i obsługi operacji asynchronicznych w deterministyczny sposób.

Wnioski

Observables są potężnym narzędziem do obsługi asynchronicznych strumieni danych w aplikacjach Angular. Zapewniają one elastyczne i reaktywne podejście do zarządzania i przekształcania danych. Rozumiejąc koncepcje stojące za observables i wykorzystując bibliotekę RxJS, można tworzyć bardziej responsywny i wydajny kod. Włączenie observables do projektów Angular zwiększy twoją zdolność do obsługi operacji asynchronicznych i tworzenia solidnych aplikacji.

Pamiętaj, aby zaimportować niezbędne operatory RxJS i zapoznać się z ogromną kolekcją dostępnych operatorów, aby dostosować zachowanie obiektów observable. Dzięki observables możesz w pełni wykorzystać zasady programowania reaktywnego i odblokować prawdziwy potencjał asynchronicznej obsługi danych w Angular. Miłego kodowania!

Bibliografia

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami