en / de
Expertisen
Methoden
Dienstleistungen
Referenzen
Jobs & Karriere
Firma
Technologie-Trends TechCast WebCast TechBlog News Events Academy

Vergleich der RxJS Higher-Order Mapping Operators

RxJS ist eine beliebte Javascript Bibliothek für reaktive Programmierung. Die Grundlage darin bilden Observables, doch erst dank den Operators wird es möglich, komplexen asynchronen Code deklarativ zu programmieren. Sie beinhaltet eine Menge von vorgefertigten Operators – so viele, dass man leicht den Überblick verlieren kann.

Dieser Blogeintrag vergleicht die beliebten Higher-Order Mapping Operators concatMap, exhaustMap, mergeMap und switchMap. Mittels Code-Beispielen wird auf die Unterschiede und ihren Einfluss auf die Datentransformation eingegangen. Zunächst wird der Aufbau der Beispiele beschrieben, danach werden ihre Resultate erklärt. Am Schluss gibt es eine kleine Zusammenfassung der Erkenntnisse.

Aufbau

Jedes der nachfolgenden Beispiele verändert bzw. transformiert ein Observable anhand eines der oben genannten Operatoren und gibt das Resultat aus. Das Observable wird einer simplen Multiplikation unterzogen. Bei jeder Transformation wird eine Verzögerung von höchstens drei Sekunden eingebaut. Dadurch kann die Netzwerklatenz simuliert werden. Um den Vergleich zwischen den Operatoren zu ermöglichen, sind alle Code-Beispiele ähnlich aufgebaut, wobei der Aufbau auf dem switchMap-Beispiel der RxJS-Dokumentation basiert [1].

console.log("insert name of operator here");
const numberObservable = of(1, 2);
const resultOfOperator = numberObservable.pipe(
  <<insert operator here>>((x) =>
    of(x * 5).pipe(delay(1000 * Math.floor(Math.random() * 3)))
  )
);
resultOfOperator.subscribe((x) => console.log('r ' + x));

Das Ergebnis der angewandten Transformationen ist in Abbildung 1 zu finden.

Reihenfolge der Resultate pro Map-Operator

Abbildung 1: Reihenfolge der Resultate pro Operator

 

Unter der Verwendung von Html, Typescript und RxJS werden nachfolgend die Operatoren und deren Rolle bei der Datenverarbeitung erläutert.

Resultate

In diesem Abschnitt werden die Resultate beschrieben.

concatMap

In Abbildung 1 ist erkennbar, dass der concatMap-Operator die Werte des äusseren Observable (im Code «numberObservable») in sequenzieller Reihenfolge verarbeitet. Ursache dafür ist das innere Observable (unten im Code hervorgehoben). Es muss fertig sein, bevor concatMap weitere Daten des «numberObservable» verarbeiten kann [2].

console.log('concatMap');
const numberObservable = of(1, 2);
const concatMapResult = numberObservable.pipe(
  concatMap((x) =>
    of(x * 5).pipe(delay(1000 * Math.floor(Math.random() * 3))) //inneres Observable
  )
);
concatMapResult.subscribe((x) => console.log('c ' + x));

exhaustMap

Auch hier werden die vorhandenen Daten mit fünf multipliziert. In Abbildung 1 ist deutlich, dass nach der Ausführung von exhaustMap zwar das Resultat «5» angezeigt wird, jedoch die «10» fehlt. Um dieses Verhalten zu verstehen, muss man die Werte des «numberObservable» als einen Datenstrom betrachten, wie das Marble-Diagramm in Abbildung 2 zeigt. Der Wert, welcher zuerst das innere Observable erreicht, wird auch zuerst transformiert. Der exhaustMap-Operator ignoriert dabei alle anderen eingetroffenen Daten, solange das innere Observable mit der Transformierung nicht fertig ist. In diesem Fall trifft «2» ein, während das innere Observable noch «1» bearbeitet, weshalb dieser Wert ignoriert wird. Daher kann man sagen, dass exhaustMap alle Werte des äusseren Observable ignoriert und nicht verarbeitet, solange das innere Observable noch aktiv ist [3]. Dabei muss betont werden, dass beide Werte verarbeitet und ausgegeben werden, wenn die Werte zeitgleich eintreffen.

console.log('exhaustMap');
const numberObservable = of(1, 2);
const exhaustMapResult = numberObservable.pipe(
  exhaustMap((x) =>
    of(x * 5).pipe(delay(1000 * Math.floor(Math.random() * 3))) //inneres Observable
  )
);
exhaustMapResult.subscribe((x) => console.log('e ' + x));
Marble-Diagramm von exhaustMap

Abbildung 2: Marble-Diagramm zeigt das Verhalten von exhaustMap

mergeMap

Gemäss der Resultate in Abbildung 1 unterscheiden sich mergeMap und concatMap nicht voneinander. Nach mehrfacher Ausführung zeigen die Abbildungen 3 und 4 allerdings ein anderes Bild: ConcatMap gibt verlässlich die Berechnungen in sequenzieller Folge aus, wohingegen der mergeMap-Operator die Resultate in beliebiger Reihenfolge ausgibt. Der Grund für dieses Verhalten liegt darin, dass der mergeMap-Operator nicht auf den Abschluss des inneren Observable wartet und daher den nächsten angekommenen Wert auch verarbeitet [4]. Das heisst, eine zeitliche Überschneidung der Verarbeitungen ist möglich, weshalb die Resultate keine sequenzielle Verarbeitungsreihenfolge aufweisen. Die Marble-Diagramme in Abbildung 5 verdeutlichen dieses Verhalten. Man sieht, dass je nach Timing entweder «5» oder «10» ausgegeben wird.

    console.log('mergeMap');
    const numberObservable = of(1, 2);
    const mergeMapResult = numberObservable.pipe(
      mergeMap((x) =>
        of(x * 5).pipe(delay(1000 * Math.floor(Math.random() * 3))) //inneres Observable
      )
    );
    mergeMapResult.subscribe((x) => console.log('m ' + x));
Mehrmaliges Ausführen von mergeMap

Abbildung 3: Mehrmaliges Ausführen von mergeMap

Resultat von concatMap

Abbildung 4: Mehrmaliges Ausführen von concatMap

Marble-Diagramme zu mergeMap

Abbildung 5: Marble-Diagramme zu mergeMap

switchMap

Abbildung 1 lässt erkennen, dass sowohl switchMap als auch exhaustMap nur einen Wert zurückgegeben haben. Dennoch hat switchMap im Unterschied zu exhaustMap nur das Resultat des letzten Werts im Observable ausgegeben. Dies liegt darin, dass sich switchMap nur für den letzten Wert des inneren Observable interessiert. Alle vorherigen Berechnungen werden abgebrochen [1]. Wie bei exhaustMap muss auch hier hervorgehoben werden, dass das Timing wichtig ist. Denn auch hier können «5» und «10» bei zeitnahem Eintreffen der Werte ausgegeben werden.

console.log('switchMap');
const numberObservable = of(1, 2);
const switchMapResult = numberObservable.pipe(
  switchMap((x) =>
    of(x * 5).pipe(delay(1000 * Math.floor(Math.random() * 3))) //inneres Observable
  )
);
switchMapResult.subscribe((x) => console.log('s ' + x));

Zusammenfassung

Zum Schluss werden die Erkenntnisse der Ausführungen zusammengefasst:

Quellen

[1] RxJS, «RxJS – switchMap,» [Online]. Available: https://rxjs.dev/api/operators/switchMap. [Zugriff am 19 November 2023].
[2] RxJS, «RxJS – concatMap,» [Online]. Available: https://rxjs.dev/api/operators/concatMap. [Zugriff am 19 November 2023].
[3] RxJS, «RxJS – exhaustMap,» [Online]. Available: https://rxjs.dev/api/operators/exhaustMap. [Zugriff am 19 November 2023].
[4] RxJS, «RxJS – mergeMap,» [Online]. Available: https://rxjs.dev/api/operators/mergeMap. [Zugriff am 19 November 2023].

 

 

 

Kommentare

Schreiben Sie einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Newsletter - aktuelle Angebote, exklusive Tipps und spannende Neuigkeiten

 Jetzt anmelden
NACH OBEN
Privacy Policy Cookie Policy
Zur Webcast Übersicht