[ Web Proxy ]
URL:
Viewing: https://developer.mozilla.org/de/docs/Web/API/Observable_API/Using_observables [Back]  [Original]

Observables verwenden - Web-APIs | MDN

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

View in English Always switch to English

Observables verwenden

Die Observable API bietet einen Mechanismus zur Verarbeitung von Wertestrmen, einschlielich asynchroner Ereignisse. Dieser Leitfaden erklrt anhand von Browser-Ereignisstrmen, wie Sie bestehende Observables transformieren, abonnieren und wieder abbestellen.

Bevor Sie fortfahren, knnen Sie die bersicht zur Observable API lesen, um sich mit den grundlegenden Konzepten vertraut zu machen.

In diesem Artikel

Ein Observable erhalten

Observable-Objekte (blicherweise Observables genannt) reprsentieren einen Strom von Werten, der beobachtet und transformiert werden kann. Der Code, der diese Werte bereitstellt, ist der Producer; der Code, der sie abonniert, empfngt und verwendet, ist der Consumer. Es gibt drei wesentliche Mglichkeiten, Observables zu erhalten:

Im Web sind EventTarget-Objekte vielleicht der hufigste Anwendungsfall fr Observables. Die Grundidee lautet: Wo Sie bisher addEventListener(eventType, handler) geschrieben haben, knnen Sie nun when(eventType).subscribe(handler) schreiben, um denselben Effekt zu erzielen. So knnen Sie beispielsweise mit when() auf click-Ereignisse am Dokument-Body reagieren:

js
document.body.when("click").subscribe((event) => {
  console.log("Clicked!", event);
});

Dies ist nicht blo ein syntaktischer Unterschied. Mit Observables knnen Sie Operationen kombinieren, etwa Ereignisse filtern, deren Daten transformieren und einen Strom beenden, wenn ein anderes Ereignis eintritt. Dieselben Operationen lassen sich nicht nur mit Ereignissen, sondern auch mit Promises, Iterables und eigenen Strmen verwenden, etwa den Beispielen fr Timer und Benachrichtigungen ber Elementgren unter Eigene Observables erstellen.

Ein Observable transformieren

Ein Observable ist ein Strom von Werten, den Sie in einen neuen Strom transformieren knnen. Ein Observable-Objekt bietet mehrere Transformationsmethoden, die denen von Iterator oder Array hneln.

Observable-Methode Entsprechung bei Iterator Beschreibung
Observable.drop() Iterator.drop() berspringt die ersten n Werte des Quell-Observables.
Observable.filter() Iterator.filter() berspringt Werte, die eine Prdikatfunktion nicht erfllen.
Observable.flatMap() Iterator.flatMap() Ordnet jedem Wert ein Observable zu und fhrt die resultierenden Observables zu einem einzigen Observable zusammen.
Observable.inspect() Keine Ruft Callbacks auf, um Werte und den Lebenszyklus des Abonnements zu untersuchen, und ermglicht dabei weitere Verkettungen.
Observable.map() Iterator.map() Ordnet jedem Wert mithilfe einer Mapping-Funktion einen neuen Wert zu.
Observable.switchMap() Keine Ordnet jedem Wert ein inneres Observable zu und gibt nur Werte des jeweils neuesten inneren Observables aus.
Observable.take() Iterator.take() bernimmt nur die ersten n Werte des Quell-Observables.
Observable.takeUntil() Keine Funktioniert wie take(), beendet den Strom aber, sobald ein zweites Observable einen Wert ausgibt.

Sie knnen diese Methoden verketten, um mehrere Transformationen anzuwenden. Anschlieend abonnieren Sie das letzte Observable, um die transformierten Werte zu empfangen.

Im folgenden Beispiel geben wir die Mauskoordinaten auf dem Bildschirm aus, wenn die Maus ber einige <div>-Elemente bewegt wird. Das HTML zeigen wir nicht, da es lediglich die <div>-Elemente und ein einzelnes <p>-Element zur Anzeige der Daten enthlt.

Im Beispiel-CSS geben wir den <div>-Elementen eine height, eine background-color und einen margin-bottom:

css
div {
  height: 150px;
  background-color: purple;
  margin-bottom: 10px;
}

Das JavaScript sieht so aus:

js
const outputElem = document.querySelector("p");

document.body
  .when("mousemove")
  .filter((e) => e.target.matches("div"))
  .map((e) => ({ x: e.clientX, y: e.clientY }))
  .subscribe((p) => {
    outputElem.textContent = `${p.x},${p.y}`;
  });

In diesem Codeausschnitt ist das <body>-Element der Seite ein EventTarget. Mit der Methode when() erhalten wir einen Strom von mousemove-Ereignissen, die darauf ausgelst werden.

Anschlieend legen wir eine Verarbeitungskette fest:

  • Observable.filter() lsst nur Ereignisse durch die Verarbeitungskette, die auf einem <div>-Element ausgelst wurden (geprft mit der Methode Element.matches()), nicht aber Ereignisse auf anderen Nachfahren von body.
  • Observable.map() wandelt die ausgelsten mousemove-Ereignisobjekte in neue Objekte um, die die Koordinaten des Mauszeigers zum Zeitpunkt des Ereignisses enthalten.

Schlielich abonniert Observable.subscribe() das Observable. Wir bergeben der Methode subscribe() eine Callback-Funktion. Dieser Callback wird jedes Mal aufgerufen, wenn ein mousemove-Ereignis den Filter passiert.

Die gerenderte Ausgabe sieht so aus:

Bewegen Sie die Maus ber den oberen Teil des Beispiels. Die Koordinaten werden nur dann im <p>-Element ausgegeben, wenn sich die Maus ber den <div>-Elementen befindet, nicht aber ber den Bereichen auerhalb der <div>-Elemente.

Hinweis: Observables sind lazy: Ereignisse werden erst durch sie weitergeleitet, wenn sie mindestens einen Abonnenten haben. Vorher speichern sie auch keine Daten zwischen. Wenn Sie im vorherigen Beispiel den Aufruf von subscribe() entfernen und innerhalb der Methoden filter() und map() Protokollausgaben hinzufgen, werden Sie feststellen, dass nichts protokolliert wird. Sobald subscribe() am Ende der Verarbeitungskette aufgerufen wird, werden auch alle vorherigen Observables in der Kette abonniert und beginnen mit der Verarbeitung der Daten.

Mit inneren Observables arbeiten

Manche Operationen erzeugen fr jeden Quellwert einen weiteren Strom: Ein Klick knnte einen Upload starten, oder eine nderung in einem Suchfeld knnte eine Anfrage auslsen. Diese Strme heien innere Observables. Wenn Sie nur map() verwenden, werden die inneren Observable-Objekte an Ihren Observer weitergegeben. Observable.flatMap() und Observable.switchMap() abonnieren sie stattdessen und leiten ihre Werte weiter.

  • flatMap() verarbeitet Quellwerte nacheinander. Die Methode wartet, bis das aktuelle innere Observable abgeschlossen ist, bevor sie den Mapper fr den nchsten Quellwert in der Warteschlange aufruft. Verwenden Sie sie, wenn jede Operation der Reihe nach abgeschlossen werden soll. Wird ein inneres Observable nie abgeschlossen, bleiben sptere Quellwerte in der Warteschlange.
  • switchMap() beendet das Abonnement des aktuellen inneren Observables, wenn ein neuer Quellwert eintrifft, ruft dann den Mapper auf und abonniert dessen Ergebnis. Verwenden Sie diese Methode, wenn nur die Ergebnisse der neuesten Operation relevant sind.

Beide Methoden wandeln das Ergebnis des Mappers mit Observable.from() um. Der Mapper kann daher auch ein Promise, ein Iterable oder ein Async Iterable zurckgeben. Ein Promise liefert seinen Erfllungswert und wird dann abgeschlossen; eine Zurckweisung wird zu einem Fehler im inneren Observable. map() dagegen leitet ein zurckgegebenes Promise als Wert weiter, ohne darauf zu warten.

Das folgende interaktive Beispiel durchsucht eine kleine Liste von Fruchtnamen. Geben Sie etwas in das Suchfeld ein, um eine Suche zu starten. Die simulierte Antwort dauert eine Sekunde, sodass Sie erneut tippen knnen, whrend eine vorherige Suche noch luft. Mit einem asynchronen Mapper knnen wir jede Suche als eine Operation behandeln:

html
<label>Search fruit: <input type="search" /></label>
<p id="results">Type a fruit name</p>
js
const searchInput = document.querySelector("input[type='search']");
const results = document.querySelector("#results");

async function search(query) {
  const fruits = ["Apple", "Apricot", "Banana", "Cherry", "Pear"];
  await new Promise((resolve) => setTimeout(resolve, 1000));
  return fruits.filter((fruit) =>
    fruit.toLowerCase().includes(query.toLowerCase()),
  );
}

const queries = searchInput.when("input").map(() => searchInput.value);

queries.switchMap(search).subscribe({
  next(data) {
    results.textContent = JSON.stringify(data);
  },
  error(error) {
    results.textContent = error.message;
  },
});

In einer echten Suchanwendung knnte search() stattdessen eine JSON-Antwort abrufen und parsen:

js
async function search(query) {
  const response = await fetch(`/search?q=${encodeURIComponent(query)}`);
  if (!response.ok) {
    throw new Error(`Search failed: ${response.status}`);
  }
  return response.json();
}

Trifft ein weiteres Eingabeereignis ein, bevor die vorherige Suche abgeschlossen ist, wird das vorherige Ergebnis nicht mehr weitergeleitet. Das Beenden des Abonnements eines aus einem Promise erstellten Observables bricht jedoch die Arbeit hinter diesem Promise nicht ab: Die vorherige Anfrage kann trotzdem abgeschlossen werden. Um die Anfrage selbst abzubrechen, geben Sie ein eigenes Observable zurck, das subscriber.signal an fetch() bergibt, wie unter Asynchrone Arbeit abbrechen gezeigt.

Eine Verarbeitungskette untersuchen

Mit Observable.inspect() knnen Sie einen Seiteneffekt ausfhren, etwa eine Protokollausgabe, whrend Werte unverndert weitergeleitet werden. Anders als subscribe() gibt die Methode ein Observable zurck und startet die Verarbeitungskette nicht selbst:

js
document.body
  .when("click")
  .inspect((event) => console.log("Click:", event.target))
  .map((event) => ({ x: event.clientX, y: event.clientY }))
  .subscribe((point) => console.log("Coordinates:", point));

Sie knnen auch ein Objekt mit den Callbacks subscribe, next, error, complete und abort bergeben, um den Lebenszyklus des Abonnements zu untersuchen. Ein inspect()-Callback kann die Verarbeitungskette beeinflussen, wenn er eine Exception auslst. Eine Exception in seinem next-Callback wird beispielsweise zu einem Fehler im zurckgegebenen Observable.

Weitere Einzelheiten finden Sie in der Referenz zu inspect().

Werte aggregieren

Die zuvor vorgestellten Methoden geben jeweils ein weiteres Observable zurck, sodass Sie mehrere Transformationen verketten knnen. Anschlieend knnen Sie das letzte Observable abonnieren, um die transformierten Werte zu empfangen.

Sie mchten aber nicht immer jeden Wert einzeln verarbeiten. Manchmal interessiert Sie nur ein einzelner aggregierter Wert aus dem gesamten Strom. Dafr bietet die Observable API die folgenden Methoden:

Observable-Methode Entsprechung bei Iterator Beschreibung
Observable.every() Iterator.every() Gibt false zurck, wenn das Prdikat fr irgendeinen Wert false zurckgibt; andernfalls true.
Observable.find() Iterator.find() Gibt den ersten Wert zurck, der ein Prdikat erfllt.
Observable.first() Keine Gibt den ersten Wert des Observables zurck.
Observable.forEach() Iterator.forEach() Ruft fr jeden Wert eine Funktion auf.
Observable.last() Keine Gibt den letzten Wert des Observables zurck.
Observable.reduce() Iterator.reduce() Aggregiert die Werte zu einem einzelnen Wert.
Observable.some() Iterator.some() Gibt true zurck, wenn das Prdikat fr irgendeinen Wert true zurckgibt; andernfalls false.
Observable.toArray() Iterator.toArray() Sammelt alle Werte in einem Array.

Alle diese Methoden geben Promises zurck, die mit den beschriebenen Ergebnissen erfllt werden. Je nach Methode wird das Promise erfllt, sobald das Ergebnis feststeht oder wenn das Observable abgeschlossen wird. Es kann auch zurckgewiesen werden, beispielsweise wenn im Observable ein Fehler auftritt oder das Abonnement abgebrochen wird.

Anders als die Transformationsmethoden abonnieren diese Aggregationsmethoden implizit: Die Verarbeitungskette beginnt Werte zu empfangen, sobald eine dieser Methoden aufgerufen wird.

Dieses Beispiel sucht innerhalb eines einzelnen Bereichs nach der ersten Mausposition, die auf beiden Achsen ber 200 liegt. Bis eine passende Position gefunden wird, zeigt es die aktuelle Position an und demonstriert dabei auch die Funktionsweise von inspect(). Klicken Sie auf Restart, nachdem eine passende Position gefunden wurde, um es erneut zu versuchen.

html
<div id="target">Move the pointer beyond 200,200.</div>
<p></p>
<button disabled>Restart</button>
css
#target {
  height: 300px;
  background-color: lavender;
}
js
const target = document.querySelector("#target");
const outputElem = document.querySelector("p");
const restart = document.querySelector("button");

function start() {
  restart.disabled = true;
  outputElem.textContent = "Move the mouse around...";
  target
    .when("mousemove")
    .map((event) => ({ x: event.offsetX, y: event.offsetY }))
    .inspect(({ x, y }) => {
      outputElem.textContent = `Target of 200,200 not yet reached (current ${x},${y})`;
    })
    .find(({ x, y }) => x > 200 && y > 200)
    .then(({ x, y }) => {
      outputElem.textContent = `Target coordinates found: ${x},${y}`;
      restart.disabled = false;
    });
}

restart.when("click").subscribe(start);
start();

Die gerenderte Ausgabe sieht so aus:

Ein Observable abonnieren

Die grundlegende Verwendung von subscribe() haben wir bereits in den vorherigen Abschnitten gezeigt. Sehen wir sie uns nun etwas genauer an.

So wie ein Promise Benachrichtigungen ber seine Erfllung oder Zurckweisung liefern kann, kann auch ein Observable verschiedene Arten von Benachrichtigungen an seine Abonnenten senden. Jede davon entspricht einer anderen Methode, die Sie an subscribe() bergeben knnen.

  • next(value): Wird aufgerufen, sobald ein neuer Wert im Observable-Strom verfgbar ist. In den obigen Beispielen haben wir eine einzelne Funktion an subscribe() bergeben. Das ist die Kurzform fr ein Objekt, das nur eine next()-Methode enthlt.
  • error(err): Wird aufgerufen, wenn das Observable einen Fehler meldet. Exceptions, die von den eigenen Callbacks des Observers ausgelst werden, werden stattdessen dem globalen Objekt als nicht abgefangene Fehler gemeldet und nicht an diesen error()-Callback bergeben.
  • complete(): Wird aufgerufen, wenn das Observable keine weiteren Werte mehr sendet. Darauf gehen wir im Leitfaden Eigene Observables erstellen nher ein. Ereignisstrme sind unendlich, knnen aber durch Aufrufe von take() oder takeUntil() begrenzt werden.

Dieses Observable wird beispielsweise nach drei Klicks abgeschlossen und protokolliert eine entsprechende Meldung:

js
document.body
  .when("click")
  .take(3)
  .subscribe({
    next(event) {
      console.log("Clicked at", event.clientX, event.clientY);
    },
    complete() {
      console.log("Observable complete");
    },
  });

Intern verfgt jedes aktive Observable-Abonnement ber eine Liste von Observern: Objekten, die keinen, einen oder mehrere dieser drei Callbacks enthalten. Sie knnen subscribe() mehrmals fr dasselbe Observable aufrufen, um mehrere Observer zu registrieren. Zum Beispiel:

js
const clickObservable = document.body.when("click");

clickObservable.subscribe((e) => {
  console.log("Observer 1: Clicked at", e.clientX, e.clientY);
});
clickObservable.subscribe((e) => {
  console.log("Observer 2: Clicked at", e.clientX, e.clientY);
});

// For every click, both observers will be called

Gleichzeitig aktive Observer teilen sich das Abonnement. Jeder empfngt die Werte, die whrend seines Abonnements ausgegeben werden; bereits ausgegebene Werte werden neuen Observern nicht erneut zugestellt. Das unterscheidet sich von einem gemeinsam genutzten Iterator: Dort bewegt der next()-Aufruf eines jeden Consumers denselben Iterator weiter, statt einen Wert an alle Consumer zu verteilen.

Hinweis: Dieses Verhalten bei gemeinsam genutzten Abonnements knnte sich ndern. Ein Vorschlag, jedem Observer einen eigenen Subscriber zu geben, wrde dazu fhren, dass jedes Abonnement eine separate Ausfhrung startet, statt ein aktives Abonnement wiederzuverwenden.

Fr dieses konkrete Beispiel wre das Verhalten gleich, aber jeder subscribe()-Aufruf wrde einen neuen Event Listener fr click registrieren, statt denselben Event Listener wiederzuverwenden.

Das Abonnement eines Observables beenden

Ein Observer kann das Observable auch abbestellen. Seine Callbacks werden dann nicht mehr aufgerufen. Hat ein Observable keine Observer mehr, wird sein gemeinsam genutztes Abonnement inaktiv, und es fhrt sogenannte Teardown-Callbacks aus. Diese Callbacks geben Ressourcen frei, beispielsweise den von when() registrierten Event Listener. Eigene Observables mssen diese Bereinigung selbst implementieren, wie unter Eigene Observables erstellen beschrieben.

Die bliche Methode zum Beenden eines Observable-Abonnements ist ein AbortController. Damit knnen Sie das Abonnement zu einem beliebigen Zeitpunkt whrend der Verarbeitung der Observable-Daten beenden. Dazu erstellen Sie einen AbortController und bergeben beim Aufruf von subscribe() dessen signal. Anschlieend knnen Sie AbortController.abort() auf dem Controller aufrufen. Dadurch werden die Abonnements aller Observer beendet, die diesem Signal zugeordnet sind.

Wir knnen beispielsweise unser frheres einfaches when()-Beispiel so ndern, dass das Abonnement endet, wenn der Benutzer irgendwo auf die Seite klickt. Die Ausgabe wird dann nicht mehr aktualisiert. Klicken Sie auf Restart, um ein neues Abonnement zu starten.

js
const outputElem = document.querySelector("p");
const restart = document.querySelector("button");

function start() {
  restart.disabled = true;
  outputElem.textContent = "Move the mouse over a purple area";
  // Create controller
  const controller = new AbortController();

  document.body
    .when("mousemove")
    .filter((e) => e.target.matches("div"))
    .map((e) => ({ x: e.clientX, y: e.clientY }))
    .subscribe(
      (p) => {
        outputElem.textContent = `${p.x},${p.y}`;
      },
      // Register observer with signal
      { signal: controller.signal },
    );

  document.body
    .when("click")
    .filter((event) => event.target !== restart)
    .take(1)
    .subscribe(() => {
      // Unsubscribe on click
      controller.abort();
      outputElem.textContent += "  Stopped. Click Restart to try again.";
      restart.disabled = false;
    });
}

restart.when("click").subscribe(start);
start();

Der Aufruf take(1) schliet den Klickstrom nach dem ersten Klick ab und entfernt seinen Event Listener. Das hnelt der Verwendung von { once: true } mit addEventListener().

Im nchsten Beispiel gibt ein weiteres Observable einen Wert aus, der die Abbruchbedingung auslst: document.body.when("click"). Die Methode takeUntil() ist eine Transformationsmethode und gibt daher ein Observable zurck. Sie knnen sie also in die Verarbeitungskette einfgen, um eine Bedingung festzulegen, unter der das Abonnement endet. Der folgende Code erzielt denselben Effekt wie das vorherige Beispiel:

js
const outputElem = document.querySelector("p");

document.body
  .when("mousemove")
  .filter((e) => e.target.matches("div"))
  .map((e) => ({ x: e.clientX, y: e.clientY }))
  // When the click event fires, unsubscribe
  .takeUntil(document.body.when("click"))
  .subscribe((p) => {
    outputElem.textContent = `${p.x},${p.y}`;
  });

Hinweis: Die Methode takeUntil() wandelt ihre Eingabe in ein Observable um. Sie knnen ein Promise bergeben, um den Strom bei dessen Erfllung zu beenden, oder synchrone beziehungsweise asynchrone Iterables, um ihn zu beenden, sobald sie ihren ersten Wert liefern, sofern es einen gibt. Wie die Umwandlung funktioniert, erfahren Sie unter Observable.from().

Mit einem AbortController knnen Sie ein Abonnement an beliebiger Stelle in Ihrem Code beenden. Separate Controller ermglichen es Ihnen, die Abonnements von Observern unabhngig voneinander zu beenden. Ein Abbruch ruft den complete-Callback des Observers nicht auf. takeUntil() dagegen schliet das Observable ab, das die Methode zurckgibt, und benachrichtigt dessen Observer ber ihre complete-Callbacks. Andere Observer, die das Quell-Observable direkt abonniert haben, bleiben abonniert.

Fehler behandeln

Ein Fehler beendet das betroffene Abonnement. Fehler aus einer Quelle werden durch die Verarbeitungskette weitergegeben. Exceptions aus Transformations-Callbacks, etwa einem map()-Mapper oder einem filter()-Prdikat, werden zu Fehlern im zurckgegebenen Observable. Der error-Callback eines Observers meldet oder behandelt den Fehler, setzt das Abonnement aber nicht fort. Hat der Observer keinen error-Callback, wird der Fehler dem globalen Objekt als nicht abgefangener Fehler gemeldet.

Mit Observable.catch() kann eine Verarbeitungskette nach einem Fehler fortfahren, indem sie einen Ersatzstrom abonniert. Der Callback erhlt den Fehler und gibt ein Observable oder einen beliebigen Wert zurck, der sich mit Observable.from() umwandeln lsst. Wenn er beispielsweise [] zurckgibt, wird der Ersatzstrom abgeschlossen, ohne einen Wert auszugeben. Die fehlgeschlagene Quelle wird dadurch nicht erneut ausgefhrt.

Bei inneren Observables ist die Position von catch() wichtig. Im Suchbeispiel beendet ein Fehler in der aktuellen Anfrage das Suchabonnement, sodass nachfolgende Eingabeereignisse keine weiteren Suchen starten. Wir knnen den Code fr das Abonnement durch den folgenden ersetzen, um Fehler abzufangen, whrend das innere Abonnement aktiv ist:

js
queries
  .switchMap((query) =>
    Observable.from(search(query)).catch((error) => {
      results.textContent = error.message;
      return [];
    }),
  )
  .subscribe((data) => {
    results.textContent = JSON.stringify(data);
  });

Hier behandelt catch() nur den Fehler der inneren Anfrage. Der leere Ersatzstrom wird abgeschlossen, whrend das uere Abonnement weiterhin auf Eingaben wartet. Wrden Sie catch() stattdessen nach switchMap() platzieren, wrde die gesamte Such-Verarbeitungskette ersetzt: Die Rckgabe von [] wrde sie dort abschlieen und das Warten auf Eingaben beenden.

js
queries
  .switchMap(search)
  .catch((error) => {
    results.textContent = error.message;
    return [];
  })
  .subscribe({
    next(data) {
      results.textContent = JSON.stringify(data);
    },
    complete() {
      console.log("Search pipeline ended");
    },
  });

Dieselbe Unterscheidung gilt fr flatMap().

Fehler aus Anfragen, deren Abonnement switchMap() bereits beendet hat, werden dadurch nicht behandelt. Wird das Promise einer solchen Anfrage spter zurckgewiesen, meldet Observable.from() den Fehler dem globalen Objekt, da sein Subscriber inaktiv ist; der catch()-Callback ist nicht mehr abonniert. Um berholte Anfragen abzubrechen und zu verhindern, dass ihr Abbruch als Fehler gemeldet wird, verwenden Sie den eigenen fetchJSON()-Producer unter Asynchrone Arbeit abbrechen. Er prft subscriber.active, bevor er eine Zurckweisung weiterleitet.

Exceptions aus Callbacks, die an subscribe() bergeben werden, verhalten sich anders: Sie werden dem globalen Objekt gemeldet, statt zu Fehlern zu werden, die ein catch() in der Verarbeitungskette behandeln knnte. Ebenso wird nicht auf das Promise gewartet, das ein asynchroner next-Callback zurckgibt. Behandeln Sie dessen Zurckweisungen selbst oder verwenden Sie flatMap() beziehungsweise switchMap(), um die asynchrone Arbeit in die Verarbeitungskette einzubinden.

Ein catch() in der Verarbeitungskette behandelt beispielsweise keine Exception, die von ihrem Observer ausgelst wird:

js
Observable.from([1, 2, 3])
  .catch(() => [0])
  .subscribe(() => {
    throw new Error("Reported as an uncaught error on window");
  });

Wenn bei einem Observer vorhersehbare Fehler auftreten knnen, behandeln Sie diese ausdrcklich:

js
queries.subscribe(async (query) => {
  try {
    results.textContent = JSON.stringify(await search(query));
  } catch (error) {
    results.textContent = error.message;
  }
});

Anders als die Variante mit switchMap() behandelt diese Variante Fehler, verwirft aber keine veralteten Ergebnisse.

Bereinigung ausfhren

Observable.finally() gibt ein Observable zurck, das die Werte und Benachrichtigungen der Quelle weiterleitet und einen Callback ausfhrt, wenn sein Abonnement durch Abschluss, Fehler oder Abbestellung endet. Damit eignet sich die Methode fr Bereinigungsaufgaben, die ein complete-Callback allein nicht abdecken wrde:

js
document.body
  .when("click")
  .take(3)
  .finally(() => console.log("Stopped observing clicks"))
  .subscribe((event) => console.log(event.target));

Im vorherigen Codeausschnitt wird der finally()-Callback ausgefhrt, nachdem das Abonnement endet gem take(3) nach drei Klicks. Er wird auch ausgefhrt, wenn das Abonnement vorzeitig abgebrochen wird. Auf ein zurckgegebenes Promise wartet die Methode nicht. Verwenden Sie finally(), um einer Verarbeitungskette eine Bereinigung hinzuzufgen. Verwenden Sie addTeardown(), um die Ressourcenbereinigung des Producers selbst zu implementieren, wie unter Eigene Observables erstellen beschrieben.

Beispiel: Zeichnen auf einem Canvas

In diesem Beispiel erstellen wir eine einfache Zeichenanwendung mit <canvas>. Sie fhrt die bisher behandelten APIs zusammen und zeigt, wie Observables dabei helfen, komplexe Logik zur Ereignisverarbeitung deklarativ umzusetzen.

HTML

Das Markup enthlt ein <canvas>-Element zum Zeichnen und ein <form> mit zwei <input>-Steuerelementen, ber die Benutzer eine neue Stiftgre und -farbe auswhlen knnen: einen Bereichsregler beziehungsweise eine Farbauswahl. Auerdem fgen wir ein <output>-Element hinzu, das den aktuellen Wert des Bereichsreglers anzeigt.

html
<canvas></canvas>
<form>
  <div>
    <label for="size">Choose pen size:</label>
    <input id="size" type="range" min="1" max="40" value="10" />
    <output for="size">10</output>
  </div>
  <div>
    <label for="color">Choose pen color:</label>
    <input id="color" type="color" />
  </div>
</form>

CSS

Im CSS lassen wir das <body>-Element die gesamte Breite und Hhe der Seite einnehmen. Auerdem platzieren wir das <form> ber dem <canvas> und verwenden Inset-Eigenschaften, damit es am oberen, linken und rechten Rand des <body> anliegt.

Der Rest des CSS ist fr das Verstndnis des Beispiels nicht wichtig. Wir erlutern ihn daher hier nicht, haben ihn aber unten vollstndig aufgefhrt.

css
* {
  box-sizing: border-box;
}

html {
  font-family: "Helvetica", "Arial";
  height: 100%;
}

body {
  margin: 0;
  height: inherit;
  overflow: hidden;
}

form {
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  padding: 10px;
  background: rgb(218 112 214 / 0.85);
  box-shadow: 0 1px 3px black;
}

form > div {
  display: flex;
  align-items: center;
}

form > div:first-child {
  margin-bottom: 10px;
}

form label {
  width: 140px;
}

form input {
  width: 100px;
}

JavaScript

In unserem Skript holen wir zuerst Referenzen auf die Elemente <canvas>, <form>, <input> und <output>:

js
const canvas = document.querySelector("canvas");
const form = document.querySelector("form");
const sizeInput = document.querySelector("[type='range']");
const sizeOutput = document.querySelector("output");
const colorInput = document.querySelector("[type='color']");

Anschlieend gleichen wir width und height des Canvas an clientWidth und clientHeight des <body> an. Das bernimmt die Funktion sizeCanvas(). Sie wird beim Start der Anwendung und bei jeder Grennderung des Fensters aufgerufen. Dazu verwenden wir when("resize").subscribe(sizeCanvas), was denselben Effekt wie addEventListener("resize", sizeCanvas) hat. Das Festlegen der Canvas-Abmessungen lscht auerdem die Zeichnung.

js
function sizeCanvas() {
  canvas.width = document.body.clientWidth;
  canvas.height = document.body.clientHeight;
}

sizeCanvas();

window.when("resize").subscribe(sizeCanvas);

Als Nchstes definieren wir die Variablen und Funktionen, die wir zum Zeichnen auf dem <canvas> bentigen. Zuerst holen wir eine Referenz auf den 2D-Rendering-Kontext des <canvas> und speichern Anfangswerte fr Stiftgre und -farbe in penSize beziehungsweise penColor. Die Funktion updatePenSize() setzt penSize auf valueAsNumber des Bereichsreglers und zeigt den Wert im <output>-Element an. Die Funktion updatePenColor() setzt penColor auf value der Farbauswahl. Diese Funktionen verarbeiten das input-Ereignis des Bereichsreglers beziehungsweise das change-Ereignis der Farbauswahl.

js
const ctx = canvas.getContext("2d");
let penSize = 10;
let penColor = "black";

function updatePenSize() {
  penSize = sizeInput.valueAsNumber;
  sizeOutput.textContent = sizeInput.value;
}

function updatePenColor() {
  penColor = colorInput.value;
}

sizeInput.when("input").subscribe(updatePenSize);
colorInput.when("change").subscribe(updatePenColor);

Kommen wir nun zu unserer zentralen Funktion draw(). Zunchst blenden wir das <form> aus, damit beim Zeichnen das gesamte <canvas> sichtbar ist. Dann setzen wir fillStyle des Canvas-Kontexts auf penColor, beginnen mit beginPath() einen Pfad, zeichnen mit arc() einen einzelnen Kreis mit der angegebenen penSize an den Koordinaten x und y des Ereignisobjekts (dazu spter mehr) und stellen die Zeichnung mit fill() auf dem Canvas dar. Zusammen mit dem spter gezeigten Observable-Code wird so bei jeder Mausbewegung ein Kreis an den aktuellen Mauskoordinaten gezeichnet.

js
function draw(e) {
  form.style.display = "none";

  ctx.fillStyle = penColor;
  ctx.beginPath();
  ctx.arc(
    e.x - canvas.offsetLeft,
    e.y - canvas.offsetTop,
    penSize,
    0,
    2 * Math.PI,
  );
  ctx.fill();
}

Als letzte Funktion definieren wir finishDraw(). Sie zeigt das <form> wieder an, wenn das Zeichnen beendet ist.

js
function finishDraw() {
  form.style.display = "block";
}

Schlielich erstellen wir ein Observable fr mousedown-Ereignisse auf dem <canvas>. Bei jedem Drcken der primren Maustaste abonniert Observable.flatMap() einen Strom von mousemove-Ereignissen, der endet, wenn die Taste losgelassen wird. Wir achten auf mouseup am document-Objekt, damit das Zeichnen auch dann endet, wenn die Maustaste auerhalb des Canvas losgelassen wird. Anschlieend extrahieren wir mit Observable.map() die Mauskoordinaten und bergeben sie mit subscribe() an draw().

js
canvas
  .when("mousedown")
  .filter((e) => e.button === 0)
  .flatMap(() => {
    const mouseUp = document
      .when("mouseup")
      .filter((e) => e.button === 0)
      .finally(finishDraw);
    return canvas.when("mousemove").takeUntil(mouseUp);
  })
  .map((e) => ({ x: e.clientX, y: e.clientY }))
  .subscribe(draw);

Die Verarbeitungskette verarbeitet eine Abfolge von mousedown mousemove mouseup:

  1. Jedes mousedown-Ereignis der primren Maustaste passiert den Filter und lst den flatMap()-Callback aus.
  2. Der Callback gibt canvas.when("mousemove").takeUntil(mouseUp) zurck. Durch das Abonnieren dieses inneren Observables wird begonnen, auf Mausbewegungen und das Loslassen der Taste zu achten.
  3. Jede Mausbewegung auf dem Canvas gelangt ber flatMap() zu map(), wo ihre Koordinaten extrahiert werden, und anschlieend zu draw().
  4. Wenn die primre Maustaste losgelassen wird, schliet Observable.takeUntil() das innere Observable ab und beendet die Abonnements beider Ereignisstrme. Der Callback von Observable.finally() wird whrend der Bereinigung ausgefhrt und ruft finishDraw() auf, um das Formular wieder anzuzeigen.
  5. Das uere mousedown-Abonnement bleibt aktiv, sodass der nchste Tastendruck eine neue Zeichenfolge startet.

Im Ergebnis reagieren wir auf mousemove-Ereignisse (genau wie im ersten Beispiel auf dieser Seite!), beginnen aber erst beim Auslsen von mousedown, auf sie zu achten, und hren beim Auslsen von mouseup wieder damit auf. Mithilfe von Observable haben wir drei parallele Ereignisstrme erfolgreich zu einer einzigen, zusammenhngenden Abfolge kombiniert.

Ergebnis

Das Beispiel wird so dargestellt:

Siehe auch


Web Proxy Viewer  |  New URL  |  Original Page