HOWTO · Angular

Angular Observable VS. Promise

In diesem Artikel erklären wir, was Observable und Promise in Angular ist. Darüber hinaus erklären wir den grundlegenden Unterschied zwischen Observable und Promise.

Auf dieser Seite

Wähle ein Promise für ein einzelnes späteres Ergebnis und ein Observable, wenn eine Aufgabe natürlicherweise Werte über einen Zeitraum hinweg liefert. Angular macht diese Abstraktionen nicht austauschbar: Entscheidend ist, welches Verhalten dein Code benötigt.

Was ist Promise in Angular

Ein Promise ist ein JavaScript-Objekt, das ein einzelnes späteres Ergebnis darstellt: die Erfüllung mit einem Wert oder die Ablehnung mit einem Grund. Es wechselt von „pending“ zu „fulfilled“ oder „rejected“. Mit den Methoden then, catch und finally kannst du Abläufe verknüpfen und Ergebnisse behandeln. Ein Promise wird genau einmal aufgelöst; ein erfülltes Promise gibt später keinen weiteren Wert aus.

Der Executor eines Promise startet, sobald das Promise erstellt wird. Die Promise-API bietet keine eingebaute Abbruch- oder Wiederholungsfunktion. Anwendungscode kann jedoch abbrechbare Arbeit oder Wiederholungslogik rund um ein Promise umsetzen. Mit wachsenden Abläufen können lange Ketten außerdem schwerer zu überblicken sein.

Was ist Observable in Angular

Ein Observable stellt einen Stream dar, der null oder mehr Werte ausgeben und anschließend abgeschlossen oder mit einem Fehler beendet werden kann. Er kann auch unbegrenzt offen bleiben, ohne eine dieser abschließenden Benachrichtigungen zu senden. In Angular gibt HttpClient RxJS-Observables zurück. Die Arbeit eines Observable beginnt üblicherweise beim Abonnieren; der genaue Zeitpunkt hängt jedoch davon ab, wie es erstellt wurde. Mit RxJS-Operatoren lassen sich Streams umwandeln und kombinieren. Eine Abmeldung beendet die Zustellung und kann die zugrunde liegende Arbeit abbrechen, sofern der Produzent dies unterstützt.

Ein Observable bietet nicht automatisch in jedem Fall Abbruch, Wiederholungen oder eine bessere Leistung. Diese Eigenschaften hängen vom Produzenten und den verwendeten Operatoren ab. RxJS stellt nützliche Werkzeuge bereit, bringt für Entwickler, die Streams noch nicht kennen, aber auch eine Lernkurve mit sich.

Die wichtigsten Unterschiede zwischen Promise und Observable in Angular

Aspekt Promise Observable
Werte Ein Erfüllungswert oder ein Ablehnungsgrund Null oder mehr next-Werte; der Stream kann abgeschlossen oder mit einem Fehler beendet werden oder ohne eine dieser Benachrichtigungen offen bleiben
Start Der Executor läuft beim Erstellen Beginnt oft beim Abonnieren; der Erstellungsprozess bestimmt den Zeitpunkt
Fehler Mit catch oder einem Ablehnungshandler behandeln Im Abonnement oder mit Operatoren wie catchError behandeln
Abbruch Keine eingebaute Abbruchmethode Abmelden beendet die Beobachtung und kann unterstützte Arbeit abbrechen
Geeignet für Ein Ergebnis, etwa eine einfache Berechnung Ereignisse, veränderliche Werte oder Abläufe, die von der Kombination mit RxJS profitieren
Wiederholung Keine eingebaute Wiederholung; rufe die erzeugende Funktion selbst erneut auf Operatoren wie retry abonnieren die Quelle nach einem Fehler erneut
Teilen Jeder Handler sieht dasselbe einzelne aufgelöste Ergebnis Ein kaltes Observable führt seinen Produzenten für jeden Abonnenten erneut aus; teile eine heiße Quelle ausdrücklich, wenn ein Lauf viele Abonnenten bedienen soll

Für eine HTTP-Anfrage in Angular eignet sich das HttpClient-Observable, wenn du RxJS-Komposition oder einen Abbruchablauf benötigst. Eine Antwort kann ein Array enthalten. Das bedeutet aber nicht, dass wegen der mehreren Array-Elemente ein Observable nötig ist: Die Anfrage liefert weiterhin einen einzelnen Antwortwert. Auch ein Promise kann diese Antwort darstellen. Wähle die Abstraktion, die zur Aufgabe und zu den umgebenden APIs passt.

Abbruch, verzögerter Start und Wiederholung im Detail

Der Abbruch zeigt den deutlichsten praktischen Unterschied. Bei einem Suchfeld mit Suche während der Eingabe sollte jeder neue Tastenschlag die Anfrage für den vorherigen Begriff fallen lassen. Mit einem HttpClient-Observable wechselt switchMap zur neuen Anfrage und meldet die alte ab, woraufhin HttpClient diese laufende Anfrage abbricht. Ein Promise bietet keinen gleichwertigen Wechsel: Nach dem Start läuft die zugrunde liegende Arbeit weiter, sofern die aufgerufene API keinen eigenen Abbruchmechanismus bereitstellt, etwa einen AbortController für fetch, der vom Promise selbst getrennt ist.

Auch der Ausführungszeitpunkt unterscheidet sich. Ein Promise startet seine Arbeit bei der Erstellung, unabhängig davon, ob jemals ein Handler angehängt wird; das Erstellen des Promise ist also bereits ein Seiteneffekt. Ein kaltes Observable ist dagegen verzögert: Es zu erstellen beschreibt die Arbeit nur, jeder subscribe-Aufruf führt den Produzenten erneut aus, und zwei Abonnenten können daher zwei getrennte Ausführungen auslösen. Code, der einen gemeinsamen Lauf erwartet, muss dies ausdrücklich angeben, etwa mit einem Sharing-Operator oder indem er ein Promise speichert.

Wiederholung und Komposition folgen demselben Muster. Ein abgelehntes Promise ist beendet; zur Wiederherstellung musst du die erzeugende Funktion in einer eigenen Schleife erneut aufrufen. Ein Observable kann seine Quelle stattdessen mit dem retry-Operator erneut abonnieren, und Zeitmuster, die um ein Promise handgeschriebene Timer und Flags bräuchten, etwa das Warten auf eine Eingabepause mit debounceTime oder das Ignorieren unveränderter Eingaben mit distinctUntilChanged, sind in RxJS einzelne Operatoren.

Auswahl in der Praxis

Greife zu einem Promise, wenn das Ergebnis wirklich einzeln und wegwerfbar ist: eine einmalige Berechnung, ein Drittanbieter-SDK, das bereits ein Promise zurückgibt, oder ein kurzer linearer Ablauf, der sich mit async und await am besten liest und nie abgebrochen werden muss. Greife zu einem Observable, wenn die Quelle von Natur aus ein Stream ist, etwa HttpClient-Antworten, die du kombinieren oder abbrechen willst, Routenparameter, Änderungen von Formularwerten, DOM-Ereignisse oder ein WebSocket, und immer dann, wenn späte Abonnenten, wiederholte Werte oder Teardown eine Rolle spielen. Behalte im Zweifel den Typ bei, den die erzeugende API bereits zurückgibt, statt an jeder Grenze zu konvertieren.

Zwischen Promise und Observable umwandeln

Die beiden Modelle arbeiten zusammen, aber die Umwandlung ist nicht kostenlos. RxJS from verpackt ein Promise als Observable, das den aufgelösten Wert einmal ausgibt, während firstValueFrom und lastValueFrom ein Observable für Code mit async und await wieder in ein Promise verwandeln. Ein HttpClient-Observable auf diese Weise umzuwandeln behält die einzelne Antwort, gibt aber den Abbruch durch Abmelden auf und beendet an dieser Stelle jede weitere Operator-Komposition. Wandle an einer klaren Grenze um, etwa am Rand eines Service, statt innerhalb wiederverwendbarer Stream-Logik.

Häufige Fehler

  • Ein Array innerhalb einer einzelnen HTTP-Antwort als mehrere Ausgaben zu behandeln. Die Anfrage gibt weiterhin nur einmal aus; nur eine wirklich wiederholende Quelle gibt wiederholt aus.
  • Eine Anfrage in ein Promise umzuwandeln und dann zu erwarten, dass das Verlassen der Seite sie abbricht. Ohne ein Abonnement, das abgemeldet werden kann, signalisiert nichts dem Produzenten zu stoppen.
  • In einer langlebigen Komponente ohne Teardown-Strategie zu abonnieren, etwa ohne async-Pipe oder takeUntilDestroyed, sodass veraltete Abonnements nach dem Entfernen der Ansicht weiter Werte empfangen.
  • Zu erwarten, dass ein aufgelöstes Promise später einen neueren Wert aufnimmt. Ein Promise speichert sein einziges Ergebnis; zum Lesen des aktuellen Zustands ist ein neuer Aufruf oder ein Stream nötig.

Beispiel für Promise und Observable in Angular

Dieses eigenständige JavaScript-Beispiel verwendet ein kleines Lehr-Observable, um Abonnements und mehrere Ausgaben zu zeigen. Es ist nicht die von Angular verwendete RxJS-Implementierung. Das Observable sendet beim Abonnieren die Werte queued und saved; diese Beschriftungen bleiben im unveränderten Beispiel auf Englisch. Der Promise-Callback läuft anschließend in einem Microtask.

Führe dieses eigenständige Beispiel aus:

class Observable {
  constructor(producer) {
    this.producer = producer;
  }

  subscribe(next) {
    const teardown = this.producer(next);
    return { unsubscribe: teardown };
  }
}

const promise = Promise.resolve("saved");
const updates = new Observable((next) => {
  next("queued");
  next("saved");
  return () => {};
});

console.log("before subscription");
updates.subscribe((value) => console.log(`Observable: ${value}`));
promise.then((value) => console.log(`Promise: ${value}`));
console.log("after scheduling");
before subscription
Observable: queued
Observable: saved
after scheduling
Promise: saved

Das Lehr-Observable ruft seinen Produzenten beim Abonnieren auf und sendet zwei Werte. Das Promise wird genau einmal aufgelöst; sein Callback läuft in einem späteren Microtask. Verwende im Anwendungscode das RxJS-Observable und dessen Abonnement-APIs. Wähle ein Promise für ein einzelnes Ergebnis und ein Observable, wenn Stream-Komposition, wiederholte Ausgaben oder Abonnementverwaltung zur Aufgabe passen.

Beispiel: Eifriges Promise, verzögertes und abbrechbares Observable

Dieses zweite eigenständige Beispiel verwendet dasselbe Lehr-Observable, um die Zeilen zu Zeitpunkt und Abbruch aus der Tabelle zu zeigen. Der Promise-Executor läuft, bevor jemand einen Handler anhängt, während der Observable-Produzent auf subscribe wartet und sein Teardown ausführt, wenn sich der Abonnent nach dem zweiten Tick abmeldet. Der Promise-Callback läuft wieder als Microtask, vor dem ersten Timer-Tick. Die Beschriftungen im unveränderten Beispiel bleiben auf Englisch.

class Observable {
  constructor(producer) {
    this.producer = producer;
  }

  subscribe(next) {
    const teardown = this.producer(next);
    return { unsubscribe: teardown };
  }
}

const promise = new Promise((resolve) => {
  console.log("Promise executor: runs at construction");
  resolve("done");
});

const ticks = new Observable((next) => {
  console.log("Observable producer: runs at subscription");
  let count = 0;
  const id = setInterval(() => next(++count), 10);
  return () => {
    clearInterval(id);
    console.log("teardown: interval cleared");
  };
});

console.log("before subscription");
const sub = ticks.subscribe((value) => {
  console.log(`tick ${value}`);
  if (value === 2) sub.unsubscribe();
});
promise.then((value) => console.log(`Promise: ${value}`));
Promise executor: runs at construction
before subscription
Observable producer: runs at subscription
Promise: done
tick 1
tick 2
teardown: interval cleared

Der Lauf bestätigt alle drei Verhaltensweisen der Reihe nach: Der Executor lief bei der Erstellung, der Produzent blieb bis zum Abonnieren untätig, und das Abmelden führte das Teardown aus, das das Intervall löschte, sodass keine weiteren Ticks eintreffen.