September 2016

Band 31, Nummer 9

Reactive Framework: Erstellen von asynchronen Webseiten mit AJAX-Unterstützung mithilfe von Reactive Extensions

Von Peter Vogel

In einem früheren Artikel habe ich beschrieben, wie das Entwurfsmuster „Beobachter“ zum Verwalten von Tasks mit langer Ausführungsdauer verwendet werden kann (msdn.com/magazine/mt707526). Am Ende dieses Artikels habe ich gezeigt, wie Microsoft Reactive Extensions (Rx) einen einfachen Mechanismus zum Verwalten einer Ereignissequenz aus einem Prozess mit langer Ausführungsdauer in einer Windows-Anwendung zur Verfügung stellen.

Wenn Rx jedoch nur zum Überwachen einer Ereignissequenz aus einem Task mit langer Ausführungsdauer verwendet wird, werden nicht alle Vorteile dieser Technologie genutzt. Rx besticht durch die Möglichkeit, beliebige ereignisbasierte Prozesse in andere Prozesse asynchron zu integrieren. In diesem Artikel verwende ich Rx beispielsweise, um asynchrone Aufrufe eines Webdiensts mithilfe eines Klicks auf eine Schaltfläche auf einer Webseite auszuführen (ein Klick auf eine Schaltfläche ist tatsächlich eine Sequenz mit einem Ereignis). In der clientseitigen Webumgebung verwende ich Rx für JavaScript (RxJS).

Rx stellt ein Standardverfahren zum Abstrahieren einer Vielzahl von Szenarien sowie zum Bearbeiten dieser mithilfe einer LINQ-ähnlichen Fluent-Schnittstelle zur Verfügung, mit der Sie Anwendungen aus einfacheren Bausteinen erstellen können. Mit Rx können Sie Ihre UI-Ereignisse in die Back-End-Verarbeitung integrieren und diese dennoch separat verwalten. Mit Rx können Sie Ihre Schnittstelle neu schreiben, ohne dass entsprechende Änderungen am Back-End (und umgekehrt) vorgenommen werden müssen.

RxJS unterstützt außerdem eine saubere Trennung zwischen HTML und Ihrem Code. Sie können auf diese Weise Datenbindungen ohne spezielles HTML-Markup nutzen. RxJS verwendet außerdem vorhandene clientseitige Technologien (z. B. jQuery). Rx besitzt einen weiteren Vorteil: Das Erscheinungsbild aller Rx-Implementierungen ist sehr ähnlich. Der RxJS-Code in diesem Artikel ähnelt stark dem Microsoft .NET Framework-Code, den ich in meinem vorherigen Artikel bereitgestellt habe. Sie können die Kenntnisse, die Sie in einer Rx-Umgebung erworben haben, problemlos in jeder beliebigen anderen Rx-Umgebung nutzen.

Erste Schritte mit RxJS

RxJS erreicht die gewünschten Ergebnisse, indem die Teile einer Anwendung in zwei Gruppen abstrahiert werden. Member der ersten Gruppe sind die beobachtbaren Objekte: im Grunde alle Objekte, die ein Ereignis auslösen können. RxJS stellt eine umfangreiche Sammlung von Operatoren zum Erstellen beobachtbarer Objekte bereit. Beobachtbare Objekte umfassen alle Objekte, die scheinbar ein Ereignis auslösen können (Rx kann z. B. Arrays in Ereignisquellen konvertieren). Mit den Rx-Operatoren können Sie auch die Ausgabe von Ereignissen filtern und transformieren. Es kann hilfreich sein, sich ein beobachtbares Ereignis als Verarbeitungspipeline für eine Ereignisquelle vorzustellen.

Member der zweiten Gruppe sind die Beobachter, die Ergebnisse von beobachtbaren Objekten annehmen und die Verarbeitung dreier Benachrichtigungen von einem beobachtbaren Objekt bereitstellen: ein neues Ereignis (mit den zugehörigen Daten), einen Fehler oder eine Ende-des-Ereignisses-Sequenz. In RxJS kann ein Beobachter ein Objekt mit Funktionen zum Verarbeiten mindestens einer der drei Benachrichtigungen oder einfach eine Sammlung von Funktionen sein (eine Funktion für jede Benachrichtigung).

Damit diese beiden Gruppen verbunden werden, abonniert ein beobachtbares Objekt mindestens einen Beobachter für seine Pipeline.

Sie können RxJS Ihrem Projekt über NuGet hinzufügen (suchen Sie in der NuGet-Bibliothek nach „RxJS-All“). Eine Warnung muss jedoch ausgesprochen werden: Als ich erstmals RxJS einem Projekt hinzugefügt habe, das mit TypeScript konfiguriert war, wurde ich vom NuGet-Manager gefragt, ob ich auch die relevanten TypeScript-Definitionsdateien verwenden wolle. Ich habe auf „Ja“ geklickt. Die Dateien wurden dann hinzugefügt, und ich erhielt ungefähr 400 Fehler des Typs „Doppelt vorhandene Definition“. Diese Option werde ich nicht noch einmal auswählen.

Außerdem sind zahlreiche Rx-Unterstützungsbibliotheken verfügbar, die nützliche Plug-Ins für RxJS bereitstellen. RxJS-DOM (verfügbar über NuGet als RxJS-Bridges-HTML) bietet z. B. Integration in Ereignisse im clientseitigen DOM sowie in jQuery. Diese Bibliothek ist für das Erstellen dynamischer Webanwendungen mit RxJS wesentlich.

Wie die meisten JavaScript-Plug-Ins für Rx basieren die neuen Features von RxJS-DOM auf dem Rx-Objekt, das den Kernpunkt der RxJS-Bibliothek bildet. RxJS-DOM fügt dem Rx-Objekt eine Eigenschaft „DOM“ hinzu, die mehrere nützliche Funktionen besitzt, z. B. auch mehrere Funktionen, die eine Brücke zu jQuery-ähnlichen Funktionen bilden. Wenn Sie z. B. einen AJAX-Aufruf mithilfe von RxJS-DOM zum Abrufen von JSON-Objekten ausführen möchten, verwenden Sie den folgenden Code:

return Rx.DOM.getJSON("...url...")

Sie benötigen nur die folgenden beiden Skripttags, um Rx und RxJS-DOM zu verwenden:

<script src="~/Scripts/rx.all.js"></script>
<script src="~/Scripts/rx.dom.js"></script>

Wenn „rx.all.js“ und „rx.dom.js“ verwendet werden, führt dies zu einer überfrachteten Lösung, weil die gesamte Funktionalität aus beiden Bibliotheken enthalten ist. Glücklicherweise verteilen Rx und RxJS-DOM ihre Funktionalität auf mehrere einfachere Bibliotheken. Sie können daher nur die Bibliotheken integrieren, die die auf der Seite benötigten Funktionen enthalten (die Dokumentation zu GitHub gibt für jeden Operator an, zu welcher Bibliothek er gehört).

Integrieren eines RESTful-Diensts

In einem typischen Szenario in einer JavaScript-Anwendung klickt ein Benutzer auf eine Schaltfläche, um ein Ergebnis aus einem Webdienst abzurufen, indem ein asynchroner Aufruf des Diensts ausgeführt wird. Der erste Schritt beim Erstellen einer RxJS-Lösung besteht im Konvertieren des Klickereignisses auf die Schaltfläche in ein beobachtbares Objekt. Durch die RxJS-/DOM-/jQuery-Integration ist dies ein einfacher Vorgang. Der folgende Code verwendet z. B. jQuery zum Abrufen eines Verweises auf ein Formularelement mit der ID getButton und erstellt dann aus dem Klickereignis dieses Elements ein beobachtbares Objekt:

var getCust = $('#getButton').get(0);
var getCustObsvble = Rx.DOM.fromEvent(getCust, "click");

Die fromEvent-Funktion ermöglicht das Erstellen eines beobachtbaren Objekts aus beliebigen Ereignisses für beliebige Elemente. RxJS enthält jedoch mehrere Schnellverfahren für die „gängigeren“ Ereignisse, z. B. auch für das Klickereignis. Ich können auch den folgenden Code zum Erstellen eines beobachtbaren Objekts aus dem Klickereignis einer Schaltfläche verwenden:

var getCustObsvble = Rx.DOM.click(getCust);

Ich kann eine umfangreichere Verarbeitungspipeline für dieses Ereignis erstellen, die die Verarbeitung in meiner Anwendung vereinfacht. Angenommen, ich möchte z. B. die Verarbeitung eines Szenarios vermeiden, in dem der Benutzer kurz nacheinander zwei Mal auf die Schaltfläche klickt (zu schnell für mich, um die Schaltfläche zu deaktivieren und so diese Benutzeraktion zu verhindern). Anstatt umfangreichen Timeoutcode zu schreiben, kann ich diesen Fall berücksichtigen, indem ich der Pipeline die debounce-Funktion hinzufüge und angebe, dass ich nur Klicks verarbeiten möchten, zwischen denen mindestens zwei Sekunden liegen:

var getCustObsvble = Rx.DOM.click(getCust).debounce(2000);

Ich kann auch Verarbeitung für das Ereignis ausführen, z. B. flatMapLatest. Auf diese Weise kann ich eine Transformationsfunktion in die Pipeline integrieren (ähnlich wie SelectMany in LINQ). Die Basisfunktion (flatMap) führt eine Transformation für jedes Ereignis in einer Sequenz aus. Die flatMapLatest-Funktion geht noch einen Schritt weiter und beseitigt ein typisches Problem bei der asynchronen Verarbeitung: Sie bricht frühere asynchrone Verarbeitung ab, wenn die Transformation ein zweites Mal aufgerufen wird und eine asynchrone Anforderung noch aussteht.

Wenn ich flatMapLatest verwende und ein Benutzer zwei Mal auf die Schaltfläche klickt (im Abstand von mehr als zwei Sekunden) bricht flatMapLatest das vorherige Ereignis ab, wenn flatMapLatest das vorherige Ereignis noch transformiert. Auf diese Weise muss ich keinen Code für den Abbruch asynchroner Verarbeitung schreiben.

Der erste Schritt bei der Verwendung von flatMapLatest besteht im Erstellen der Transformationsfunktion. Zu diesem Zweck verwende ich eine Funktion als Wrapper für den getJSON-Aufruf, den ich weiter oben gezeigt habe. Da meine Funktion an das Klicken auf eine Schaltfläche gebunden ist, benötige ich keine Daten von der Seite (diese Funktion verdient tatsächlich kaum die Bezeichnung „Transformation“, weil sie die Eingaben des Ereignisses ignoriert).

Die folgende Funktion, die eine Anforderung für einen Web-API-Dienst ausgibt, verwendet bei diesem Vorgang jQuery zum Abrufen der Kunden-ID aus einem Element auf der Seite:

function getCustomer()
{
  return Rx.DOM.getJSON("api/customerorders/customerbyid/"
    + $("custId").val());
}

Mit RxJS müssen keine Rückrufe für diese Anforderung bereitgestellt werden. Die Ergebnisse des Aufrufs werden durch das beobachtbare Objekt an die Beobachter übergeben.

Zum Integrieren dieser Transformation in meine Verarbeitungskette rufe ich einfach flatMapLatest aus meinem Beobachter auf und übergeben einen Verweis auf die Funktion:

var getCustObsvble = Rx.DOM.click(getCust).debounce(2000).flatMapLatest(getCustomer);

Ich muss nun Verarbeitungsfunktionen für das beobachtbare Objekt abonnieren. Ich kann bis zu drei Funktionen im Abonnement zuweisen: eine Funktion zum Verarbeiten der Benachrichtigung, wenn ein neues Ereignis empfangen wird, eine Funktion zum Melden von etwaigen Fehlerbenachrichtigungen und eine Funktion zum Verarbeiten einer Benachrichtigung, die am Ende der Ereignissequenz gesendet wird. Da meine Verarbeitungspipeline für das Klickereignis insbesondere dafür konzipiert ist, eine Sequenz mit einem Ereignis zu generieren, muss ich keine Funktion „Ende der Sequenz” bereitstellen.

Der sich ergebende Code zum Zuweisen der beiden erforderlichen Funktionen könnte wie folgt aussehen:

var getCustObsvble.subscribe(
  c   => $("#custName").val(c.FirstName),
  err => $("Message").text("Unable to retrieve Customer: "
    + err.description)
);

Wenn ich der Meinung bin, dass diese Funktionssammlung auch anderswo nützlich sein könnte (oder ich nur meinen Abonniercode vereinfachen möchte), kann ich ein Beobachterobjekt erstellen. Ein Beobachterobjekt besitzt die gleichen Funktionen, die ich schon zuvor verwendet habe. Diese sind nur Eigenschaften mit den Namen onNext, onError und onComplete zugewiesen. Da ich onComplete nicht verwenden muss, sieht mein Beobachterobjekt wie folgt aus:

var custObservr = {
  onNext:  c   => $("#custName").val(c.FirstName),
  onError: err => $("#Message").text("Unable to retrieve Customer: "
     + err.description)
};

Mit einem separaten Beobachter wird der Code einfacher, den mein beobachtbares Element zum Abonnieren des Beobachters verwendet:

var getCustObsvble.subscribe(custObservr);

Wenn ich alle diese Komponenten nun zusammenfüge, benötige ich nur eine ready-Funktion für die Seite, die die Schaltfläche abruft, eine Pipeline anfügt, die die Schaltfläche in ein sehr durchdachtes beobachtbares Objekt umwandelt, und dann einen Beobachter für diese Pipeline abonniert. Da RxJS eine Fluent-Schnittstelle implementiert, wären nur zwei Codezeilen erforderlich: eine Zeile jQuery-Code zum Abrufen des Schaltflächenelements und eine weitere RxJS-Codezeile zum Erstellen der Pipeline und Abonnieren meines Beobachters. Durch das Aufschlüsseln der RxJS-Pipelinekonstruktion und des Abonniercodes in mehr als zwei Zeilen wird die ready-Funktion für den nächsten Programmierer leichter lesbar.

Die endgültige Version meiner ready-Funktion sieht wie folgt aus:

$(function () {
  var getCust = $('#getButton').get(0);
  var getCustObsvble =
    Rx.DOM.click(getCust).debounce(2000).flatMapLatest(getCustomer);
  getCustObsvble.subscribe(custObservr);
});

Als Bonus wird dieser Prozess, der das Klickereignis, den Aufruf des Webdiensts sowie die Datenanzeige integriert, asynchron ausgeführt. RxJS abstrahiert all die hässlichen Details, und ich muss mich nicht darum kümmern, dass dies funktioniert.

Abstrahieren von Ereignissequenzen

Durch Abstrahieren des Prozesses (und Bereitstellen einer reichhaltigen Sammlung von Operatoren) sehen in Rx viele Dinge ähnlich aus. Daher können Sie erhebliche Änderungen an Ihrem Prozess vornehmen, ohne erhebliche strukturelle Änderungen an Ihrem Code vornehmen zu müssen.

Da RxJS-DOM ein beobachtbares Objekt mit einem Ereignis (einen Klick auf eine Schaltfläche) ähnlich wie ein beobachtbares Objekt behandelt, das eine kontinuierliche Ereignissequenz generiert (z. B. „mousemove“), kann ich eine erhebliche Änderung an meiner Schnittstelle vornehmen, ohne viel an meinem Code ändern zu müssen. Ich kann mich z. B. dafür entscheiden, die Kundendaten abzurufen, sobald der Benutzer die Kunden-ID eingibt, anstatt den Benutzer die Anforderung des Webdiensts durch Klicken auf eine Schaltfläche auslösen zu lassen.

Im ersten Schritt muss ich das Element ändern, dessen Ereignisse beobachtet werden sollen. In diesem Fall bedeutet dies, dass anstelle der Schaltfläche auf der Seite das Textfeld auf der Seite verwendet wird, das die Kunden-ID enthält (außerdem werde ich auch den Namen der Variablen ändern, die das Element enthält):

var getCustId = $('#custId').get(0);

Ich könnte eine beliebige Anzahl von Ereignissen für dieses Textfeld in ein beobachtbares Objekt umwandeln. Wenn ich z. B. das blur-Ereignis für das Textfeld verwenden würde, würde ich den Aufruf des Webdiensts ausführen, wenn der Benutzer das Textfeld beendet. Ich wünsche mir jedoch ein dynamischeres Verhalten. Und entscheide mich deshalb dafür, das Kundenobjekt abzurufen, sobald der Benutzer „genügend“ Zeichen in das Textfeld eingegeben hat. Dies bedeutet, stattdessen das keyup-Ereignis zu verwenden, das eine Sequenz von Ereignissen generiert: ein Ereignis für jeden Tastaturanschlag.

Diese Änderung an meiner Pipeline sieht wie folgt aus:

var getCustObsvble = Rx.DOM.keyup(getCustId)

Wenn der Benutzer Zeichen eingibt, kann dies dazu führen, dass meine Transformationsfunktion mehrfach aufgerufen wird. Wenn der Benutzer die Eingaben schneller vornimmt, als ich die Kundenobjekte abrufen kann, liegen sogar mehrere gestapelte Anforderungen vor. Ich könnte nun flatMapLatest zum Bereinigen dieser Anforderungen verwenden. Es gibt jedoch eine bessere Lösung: In meinem Managementsystem für Kundenbestellungen ist eine Kunden-ID immer vier Zeichen lang. Daher ist es nur dann sinnvoll, meine Transformationsfunktion aufzurufen, wenn genau vier Zeichen im Textfeld enthalten sind (nur zur Klarstellung: Da meine Pipeline nun die Kunden-ID annimmt und ein vollständiges Kundenobjekt zurückgibt, führt meine Transformationsfunktion nun tatsächlich eine „Transformation“ aus).

Zum Implementieren dieser Bedingung muss ich meiner Pipeline einfach nur die Rx-Filterfunktion hinzufügen. Diese Filterfunktion funktioniert sehr ähnlich wie eine Where-Klausel in LINQ: Sie muss von einer anderen Funktion (der selector-Funktion) übergeben werden, die einen Test enthält und einen booleschen Wert basierend auf Daten zurückgibt, die dem letzten Ereignis zugeordnet sind. Nur die Ereignisse, die den Test bestehen, werden an den abonnierten Beobachter übergeben. An die Filterfunktion wird automatisch das letzte Ereignisobjekt in der Sequenz übergeben. Ich kann daher im Test meiner selector-Funktion die target-Eigenschaft des Ereignisobjekts zum Abrufen des aktuellen Werts des Textfelds verwenden und dann die Länge dieses Werts überprüfen.

Ich nehme eine weitere Änderung an meiner Pipeline vor: Ich entferne die debounce-Funktion, weil schwer zu erkennen ist, welche Vorteile sie in dieser neuen Schnittstelle mit sich bringt. Nach einer erheblichen Änderung an den Interaktionen meiner Schnittstelle ist der überarbeitete Code, der mein beobachtbares Objekt erstellt und meine Beobachter abonniert, trotzdem strukturell mit meiner vorherigen Version identisch:

var getCustObsvble = Rx.DOM.keyup(getCustId)
                       .filter(e => e.target.value.length == 4)
                       .flatMapLatest(getCustomer);
getCustObsvble.subscribe(custObservr);

Und ich muss natürlich überhaupt keine Änderungen an meinen cust­Observr-Funktionen vornehmen: Diese sind von meinen Änderungen an der Schnittstelle isoliert.

Ich kann eine weitere optionale Änderung an meiner Transformationsfunktion vornehmen. Meine Transformationsfunktion wurde immer durch drei Parameter übergeben. Diese habe ich nur ignoriert, als meine Ereignisquelle eine Schaltfläche war. Der erste Parameter, der an die flatMapLatest-Transformationsfunktion übergeben wird, ist das Ereignisobjekt für das beobachtete Ereignis. Ich kann dieses Ereignisobjekt zum Beseitigen des jQuery-Codes nutzen, der die Kunden-ID abgerufen hat. Ich rufe nun stattdessen den Wert des Textfelds aus dem Ereignisobjekt ab. Durch diese Änderung wird die Kopplung des Codes lockerer, weil meine Transformationsfunktion nicht mehr an ein bestimmtes Objekt auf der Seite gebunden ist.

Meine neue Transformationsfunktion sieht nun wie folgt aus:

function getCustomer(e)
{
  return Rx.DOM.getJSON("api/customerorders/customerbyid/"
    + e.target.value);
}

Hierin besteht die Leistungsfähigkeit der Rx-Abstraktion: Für den Wechsel von einem Element, das nur ein Ereignis generiert, zu einem Element, das eine Sequenz von Ereignissen generiert, war nur das Herumspielen mit einer einzigen Codezeile erforderlich, aus der meine Pipeline besteht (außerdem bot sich die Möglichkeit, meine Transformationsfunktion zu optimieren). Von all den Änderungen, die ich vorgenommen habe, verursacht wahrscheinlich das Umbenennen der Variablen, die mein input-Element enthält, am ehesten Probleme. Durch diese Änderung wird außerdem klar, dass der Schlüssel zum Erfolg bei Rx wie auch bei LINQ darin liegt, sich mit den verfügbaren Operatoren vertraut zu machen.

Abstrahieren von Webdienstaufrufen

Durch eine Rx-Abstraktion sehen Änderungen der Schnittstelle sehr ähnlich aus. Eine gute Abstraktion sollte das Gleiche für die Back-End-Verarbeitung leisten. Was geschieht z. B., wenn die Seite so überarbeitet wird, dass ich nicht nur Kundendaten sondern auch einige Bestellungen des Kunden abrufe? Damit dieses Beispiel interessanter wird, gehe ich davon aus, dass keine Navigationseigenschaft vorhanden ist, die das Abrufen der Bestellungen als Teil des Kundenobjekts ermöglicht, und ich daher einen zweiten Aufruf ausführen muss.

Mit Rx muss ich zuerst eine zweite Transformationsfunktion erstellen, die eine Kunden-ID in eine Sammlung von Kundenbestellungen konvertiert:

function getCustomerOrders(e) {
  return Rx.DOM.getJSON("customerorders/ordersbycustomerid/"
    + e.target.value);
}

Anschließend muss ich ein zweites beobachtbares Objekt erstellen, das diese Transformation verwendet (dieser Code sieht dem Code sehr ähnlich, der mein beobachtbares Kundenelement erstellt):

var getOrdersObsvble = Rx.DOM.keyup(getCustId)
       .filter(e => e.target.value.length == 4)
       .flatMapLatest(getCustomerOrders);

An diesem Punkt erwarten Sie vielleicht, dass das Kombinieren und Koordinieren dieser beobachtbaren Objekte mit einem erheblichen Arbeitsaufwand verbunden ist. Da mit Rx jedoch alle beobachtbaren Objekte recht ähnlich aussehen, können Sie die Ausgabe aus mehreren beobachtbaren Objekten in einer einzelnen Sequenz kombinieren, die von einem einzelnen (und relativ einfachen) Beobachter verarbeitet werden kann.

Wenn z. B. mehrere beobachtbare Objekte Bestellungen aus mehreren Quellen abrufen, kann ich die merge-Funktion von Rx verwenden, um alle Bestellungen in eine einzelne Sequenz zu mergen, für die ich einen einzelnen Beobachter abonnieren kann. Der folgende Code kombiniert z. B. beobachtbare Objekte, die aktuelle Bestellungen abrufen (getCurrentOrdersObsvble), mit geposteten Bestellungen (getPostedOrdersObsvle). Anschließend übergibt der Code die sich ergebende Sequenz an einen einzelnen Beobachter namens allOrdersObservr:

Rx.Observable.merge(getCurrentOrdersObsvble, getPostedOrdersObsvble)
             .subscribe(allOrdersObservr);

In meinem Fall möchte ich eine interessantere Variante verwenden: das Kombinieren eines beobachtbaren Objekts, das ein Kundenobjekt abruft, mit einem beobachtbaren Objekt, das alle Bestellungen für diesen Kunden abruft. Glücklicherweise stellt RxJS einen Operator für diesen Fall bereit: combineLatest. Der Operator combineLatest nimmt zwei beobachtbare Objekte sowie eine processing-Funktion an. Die processing-Funktion, die Sie an combineLatest übergeben, wird an das letzte Ergebnis aus jeder Sequenz übergeben. Sie ermöglicht das Angeben, wie die Ergebnisse kombiniert werden sollen. In meinem Fall stellt combineLatest umfangreichere Funktionen als benötigt zur Verfügung, weil ich nur ein Ergebnis aus jedem beobachtbaren Objekt erhalte: das Kundenobjekt aus einem beobachtbaren Objekt und alle Kundenbestellungen aus dem anderen beobachtbaren Objekt.

Im folgenden Code füge ich dem Kundenobjekt eine neue Eigenschaft (namens „Orders“) aus meinem ersten beobachtbaren Objekt hinzu und übernehme dann das Ergebnis aus dem zweiten beobachtbaren Objekt in diese Eigenschaft. Schließlich abonniere ich einen Beobachter namens CustOrdersObsrvr zum Verarbeiten meines neuen Kunden- und Bestellungenobjekts:

Rx.Observable.combineLatest(getCustObsvble, getOrdersObsvble,
                           (c, ords) => {c.Orders = ord; return c;})
             .subscribe(CustOrdersObsrvr);

Mein custOrdersObservr kann nun mit dem neuen Objekt zusammenarbeiten, das ich erstellt habe:

var custOrdersObservr = {
  onNext: co => {
    // ...Code to update the page with customer and orders data...               
  },
  onError: err => $("#Message").text("Unable to retrieve Customer and Orders: "
    + err.description)
};

Zusammenfassung

Durch Abstrahieren der Komponenten einer Anwendung in nur zwei Arten von Objekten (beobachtbare Objekte und Beobachter) und Bereitstellen einer reichhaltigen Sammlung von Operatoren zum Verwalten und Transformieren der Ergebnisse aus beobachtbaren Objekten stellt RxJS ein hochflexibles (und verwaltbares) Verfahren zum Erstellen von Webanwendungen zur Verfügung.

Natürlich birgt die Verwendung einer leistungsfähigen Bibliothek, die komplexe Prozesse in einfachen Code abstrahiert, auch eine Gefahr: Wenn Ihre Anwendung eine nicht erwartete Aktion ausführt, kann das Debuggen eines Problems einem Albtraum gleichkommen, weil die einzelnen Codezeilen nicht angezeigt werden, die durch die Abstraktion eliminiert wurden. Selbstverständlich müssen Sie diesen ganzen Code trotzdem nicht schreiben, und (wahrscheinlich) wird die Abstraktionsschicht dennoch weniger Fehler aufweisen als der Code, den Sie andernfalls geschrieben hätten. Dies ist die übliche Abwägung zwischen Leistungsfähigkeit und Sichtbarkeit.

Der eigentlich Vorteil bei Ihrer Entscheidung für RxJS besteht darin, dass Sie erhebliche Änderungen an Ihrer Anwendung vornehmen können, ohne erhebliche Änderungen an Ihrem Code vornehmen zu müssen.


Peter Vogelist Systemarchitekt und Chef von PH&V Information Services. PH&V bietet ein vollumfängliches Beratungsangebot vom Entwurf der Benutzererfahrung über die Objektmodellierung bis zum Datenbankentwurf. Sie erreichen ihn unter peter.vogel@phvis.com.

Unser Dank gilt den folgenden technischen Experten von Microsoft für die Durchsicht dieses Artikels: Stephen Cleary, James McCaffrey und Dave Sexton
Seit 16 Jahren beschäftigt sich Stephen Cleary mit Multithreading und asynchroner Programmierung und nutzt die Async-Unterstützung in Microsoft .NET Framework seit der ersten Community Technology Preview. Er ist der Autor von „Concurrency in C# Cookbook“ (O’Reilly Media, 2014). Seine Homepage und seinen Blog finden Sie unter stephencleary.com.

Dr. James McCaffrey ist in Redmond (Washington) für Microsoft Research tätig. Er hat an verschiedenen Microsoft-Produkten mitgearbeitet, unter anderem an Internet Explorer und Bing. Dr. McCaffrey erreichen Sie unter jammc@microsoft.com.