switchMap() vs mergeMap() – czym się różnią?

RxJS logo

W dzisiejszym wpisie poruszę temat różnic pomiędzy operatorami switchMap() i mergeMap() z biblioteki RxJS – czym się różnią i kiedy mogą okazać się przydatne?

Po co tak właściwie nam te operatory?

Operatory switchMap() i mergeMap() dają nam możliwość subskrypcji do observabli/subjectów z uniknięciem antywzorca zagniżdżonych subskypcji (ang. nested subscriptions). Czyli zamiast:

this.myService.getCustomerId().subscribe((id: number) => {
      this.myService
        .getCustomerData(id)
        .subscribe((data: customerData) => this.updateView(data));
    });

Możemy uzyskać taki kod z wykorzystaniem operatora switchMap:

 this.myService
      .getCustomerId()
      .pipe(switchMap((id: number) => this.myService.getCustomerData(id)))
      .subscribe((data: customerData) => this.updateView(data));

switchMap()

Operator switchMap() jest jednym z wielu pipe operatorów w bibliotece RxJS. Używamy go w momencie kiedy spodziewamy się dużej ilości subskrypcji wewnętrznych ale z pewnych względów nie zależy nam aby wszystkie zostały wykonane a tylko ta ostatnia.

Przykład w kodzie:

const saveLocation = (location) => {
  return of(location).pipe(delay(500));
};

const click$ = fromEvent(document, 'click');
let counter = 0;

click$
  .pipe(
    switchMap((e: MouseEvent) => {
      return saveLocation({
        timestamp: Date.now(),
        counter: counter++,
      });
    })
  )
  .subscribe((r) => console.log('Saved!', r));

Ten sam przykład na stackblitz.

Dzięki zastosowaniu operatora delay() możemy zasymulować wykonywanie się requestu. Jak widać po wielokrotnym kliknięciu na stronie, w naszej konsoli ukazuje się taki oto obraz:

Żądania od 0 do 2 nie zostały wykonane (patrz zmienna „counter”). Wykonało się natomiast tylko ostatnie kliknięcie (numer 3).

mergeMap()

Operatora mergeMap() używamy kiedy zależy nam aby każde żądanie zostało wykonane w momencie jego nadejścia (nawet wtedy kiedy poprzednie nie zostało jeszcze zakończone).

Przykład w kodzie prawie identyczny jak wyżej, jedynie zmieniony operator:

const saveLocation = (location) => {
  return of(location).pipe(delay(500));
};

const click$ = fromEvent(document, 'click');
let counter = 0;

click$
  .pipe(
    mergeMap((e: MouseEvent) => {
      return saveLocation({
        timestamp: Date.now(),
        counter: counter++,
      });
    })
  )
  .subscribe((r) => console.log('Saved!', r));

Przykład użycia mergeMap() na stackblitz.

Wynik w konsoli:

Jak widać każda subskrypcja została wykonana.

Podsumowanie

Różnice pomiędzy wspomnianymi operatorami nie są zbyt wielkie ale warto je znać aby w prawidłowy sposób korzystać z nich w trakcie kodowania. Dla chętnych pozostawiam fajny wykres, które być może jeszcze bardziej rozjaśni ten temat. Dziękuję za uwagę i do następnego wpisu!