Dieser Inhalt wurde automatisch aus dem Englischen übersetzt, und kann Fehler enthalten. Erfahre mehr über dieses Experiment.

View in English Always switch to English

Observable API

Eingeschränkt verfügbar

Diese Funktion ist nicht Baseline, da sie in einigen der am weitesten verbreiteten Browser nicht funktioniert.

Experimentell: Dies ist eine experimentelle Technologie
Überprüfen Sie die Browser-Kompatibilitätstabelle sorgfältig vor der Verwendung auf produktiven Webseiten.

Die Observable API bietet einen Mechanismus zur Verarbeitung von Werteströmen, einschließlich asynchroner Ereignisse. Sie können eine Verarbeitungskette definieren, um diese Werte zu filtern und umzuwandeln, sie abonnieren, um sie zu empfangen, und das Abonnement beenden, wenn sie nicht mehr benötigt werden.

Konzepte und Verwendung

Ereignisse und reaktive Programmierung

Ereignisse sind grundlegend für die Webentwicklung und die JavaScript-Programmierung im Allgemeinen. Sie gehen von EventTarget-Objekten aus:

js
element.addEventListener("click", handler1);
element.addEventListener("click", handler2);

Wenn der Benutzer auf das element klickt, sendet das vom Browser verwaltete Element eine Benachrichtigung an jeden seiner Event-Listener – in diesem Fall handler1 und handler2. Die Benachrichtigung ist ein Event-Objekt, das weitere Informationen und Daten zu dem Ereignis bereitstellt. Der jeweilige Handler führt daraufhin eine Aktion aus.

Dieses Paradigma wird als reaktive Programmierung bezeichnet: Aktionen werden als Reaktion auf einen Auslöser ausgeführt. Abstrakt betrachtet gibt es vier grundlegende Arten reaktiver Programmierung:

  • Einmaliges Abrufen (Single pull): Der Initiator empfängt einen einzelnen Wert. Normale Funktionen setzen dies um: Bei const result = action(); ist der Aufrufer der Funktion action() der Initiator, der einen einzelnen Wert result aus der Funktion abruft.
  • Einmaliges Senden (Single push): Der Initiator sendet einen einzelnen Wert, und der Empfänger wartet, bis er diesen Wert erhält. Promises setzen dies um: Bei promise.then((value) => { ... }); ist die Promise-Implementierung der Initiator. Wenn das Promise erfüllt wird, ruft sie die Callback-Funktion genau einmal auf und übergibt dabei einen einzelnen Wert.
  • Mehrmaliges Abrufen (Multiple pulls): Der Initiator pausiert die Ausführung der Datenquelle und setzt sie fort, um nach und nach mehrere Werte zu empfangen. Iteratoren setzen dies um: Bei const result1 = iterator.next(); const result2 = iterator.next(); ist der Aufrufer der Methode next() der Initiator, der mehrere Werte aus dem Iterator abruft.
  • Mehrmaliges Senden (Multiple pushes): Der Initiator sendet mehrere Werte, und die Ausführung des Empfängers pausiert zwischen dem Empfang dieser Werte. Ereignisse setzen dies um: Bei element.addEventListener("click", handler); ist das Element der Initiator. Es ruft die Funktion handler mehrfach auf, wenn der Benutzer auf das Element klickt, und übergibt dabei mehrere Ereignisobjekte.

Ereignisströme verknüpfen

Das Problem beim Modell mit addEventListener() besteht darin, dass sich Event-Handler nicht einfach miteinander verknüpfen lassen. Jeder Handler wird für sich ausgeführt und hat nicht automatisch Kenntnis von den anderen Ereignissen einer Abfolge. Wenn Sie beispielsweise gezielt auf ein mousemove-Ereignis reagieren möchten, das nach einem mousedown-Ereignis eintritt, müssen Sie den gemeinsamen Zustand selbst verwalten. Das führt zu komplexem und fehleranfälligem Code.

Die Observable API löst dieses Problem, indem sie Ereignisströme mit Methoden wie Observable.map() und Observable.filter() deklarativ erstellt und verarbeitet. Sie ersetzt das ereignisgesteuerte Modell nicht grundsätzlich, sondern verändert die Art, wie Sie Ihren Code organisieren – ähnlich wie Promises gegenüber herkömmlichen Callbacks die Art verändert haben, wie asynchroner Code geschrieben wird.

Hinweis: Die Observable API ist nicht grundsätzlich mit der Event API verknüpft. Die Verarbeitung von Ereignissen ist zwar ein wichtiger Anwendungsfall für Observables, doch Observables können beliebige Datenströme darstellen, nicht nur Ereignisse. Daher ähnelt die Observable API eher einem Sprachprimitiv als einer ausschließlich für das Web bestimmten API, vergleichbar mit Promise oder Iterator. Aus historischen Gründen wird sie außerhalb von TC39 spezifiziert, ebenso wie AbortController oder Streams.

Observables beziehen

In der Observable API stellt ein Observable einen Wertestrom dar, und ein Observer empfängt Benachrichtigungen darüber mittels Callbacks. Der Code, der Werte sendet, wird als Produzent bezeichnet; der Code, der diese Werte nutzt, als Konsument. Den Konsumentencode schreiben Sie in der Regel mithilfe von Methoden der Schnittstelle Observable, um Werte aus Observables umzuwandeln und zu verarbeiten. Wenn Sie selbst ein Observable implementieren, schreiben Sie den Produzentencode mithilfe der Schnittstelle Subscriber, um Werte an die Observer zu senden. Es gibt drei wesentliche Möglichkeiten, Observables zu beziehen:

  • Die Methode EventTarget.when() gibt ein Observable zurück, das einen Strom von Ereignissen darstellt, die auf dem EventTarget ausgelöst werden. Auch Bibliotheken können Observables zurückgeben.
  • Mit dem Konstruktor Observable() können Sie eigene Observables erstellen.
  • Mit der statischen Methode Observable.from() können Sie Objekte wie Promises und Iterables in Observables umwandeln.

Werte umwandeln, abonnieren und Abonnements beenden

Ein Observable lässt sich mit verschiedenen Methoden umwandeln, die neue Observables zurückgeben, beispielsweise Observable.map() und Observable.filter().

Um Werte von einem Observable zu empfangen, abonnieren Sie es mit der Methode Observable.subscribe(). Alternativ können Sie alle Werte mit Methoden, die ein Promise zurückgeben, wie Observable.reduce(), zusammenfassen. Observables werden verzögert ausgewertet: Sie beginnen erst dann, Werte zu erzeugen, wenn sie mindestens einen Abonnenten haben.

Sie können das Abonnement eines Observables auch mit einem AbortController oder bestimmten Methoden wie Observable.takeUntil() beenden.

Betrachten wir ein kurzes Beispiel:

js
document.body
  .when("mousedown")
  .filter((e) => e.target.matches("body"))
  .map((e) => ({ x: e.clientX, y: e.clientY }))
  .subscribe((p) => {
    console.log(`${p.x},${p.y}`);
  });

In diesem Codeausschnitt ist das <body>-Element der Seite ein EventTarget. Mit der Methode when() erhalten wir einen Strom von mousedown-Ereignissen, die auf diesem Element ausgelöst werden.

Anschließend definieren wir eine Verarbeitungskette:

  • Observable.filter() lässt nur Ereignisse durch die Verarbeitungskette, die auf dem <body>-Element selbst ausgelöst wurden – geprüft mit der Methode Element.matches() – und nicht auf dessen Nachfahren.
  • Observable.map() wandelt die ausgelösten mousedown-Ereignisobjekte in neue Objekte um, die die Koordinaten des Mauszeigers zum Zeitpunkt des Ereignisses enthalten.
  • Observable.subscribe() abonniert das Observable. Der Callback gibt die Mauskoordinaten jedes Mal auf der Konsole aus, wenn ein mousedown-Ereignis den Filter passiert.

Möglicherweise fällt Ihnen auf, dass dieses Paradigma Iteratoren sehr ähnlich ist. Auch bei ihnen können wir Werte mit map() und filter() umwandeln und mit next() und toArray() verarbeiten. Sowohl Observables als auch Iteratoren stellen Datenströme dar. Der entscheidende Unterschied ist, wie bereits erwähnt, dass Iteratoren nach dem Pull-Prinzip funktionieren (der Konsument entscheidet, wann er Werte empfängt), während Observables nach dem Push-Prinzip funktionieren (der Produzent entscheidet, wann er Werte sendet).

Weitere Informationen zu diesen Konzepten finden Sie unter Observables verwenden und Eigene Observables erstellen.

Schnittstellen

Observable

Stellt einen Wertestrom dar, der abonniert werden kann.

Subscriber

Stellt ein Abonnement eines Observable-Wertestroms dar und enthält Methoden zur Verwaltung des Lebenszyklus dieses Abonnements.

Erweiterungen anderer Schnittstellen

EventTarget.when()

Gibt ein Observable zurück, das einen Strom von Ereignissen darstellt, die auf dem EventTarget ausgelöst werden.

Beispiele

Vollständige Beispiele finden Sie unter Observables verwenden und Eigene Observables erstellen.

Spezifikationen

Spezifikation
Unknown specification

Browser-Kompatibilität

Siehe auch