Ist es eine gute Praxis, Observable mit async/await zu verwenden?

Lesezeit: 11 Minuten

Benutzer-Avatar
Thach Huynh

Ich verwende Angular 2 Common http, das ein Observable zurückgibt, aber ich habe ein Problem, dass mein Code ein Mesh mag, wenn ich einen verschachtelten Observable-Aufruf verwende:

this.serviceA.get().subscribe((res1: any) => {
   this.serviceB.get(res1).subscribe((res2: any) => {
       this.serviceC.get(res2).subscribe((res3: any) => {

       })
   })
})

Jetzt möchte ich async/await verwenden, um dies zu vermeiden, aber async/await funktioniert nur mit Promise. Ich weiß, dass Observable in Promise konvertiert werden kann, aber wie ich weiß, ist es keine gute Praxis. Also was soll ich hier machen?

Übrigens, es wäre nett, wenn mir jemand einen Beispielcode geben könnte, um dies mit async/await zu lösen: D

  • Wenn Sie keine der Funktionen von Observables verwenden, könnten Sie damit durchkommen. Was die gute Praxis betrifft, wahrscheinlich nicht.

    – Tizian Cernicova-Dragomir

    1. April 2018 um 9:29 Uhr


  • Das Verschachteln von Abonnements ist jedoch eine schlechte Praxis: Sie kommen zurück in die Callback-Hölle, die Promises und Observable eigentlich verhindern sollen

    – Pac0

    1. April 2018 um 9:29 Uhr


  • Wenn Sie Observables verketten möchten, verwenden Sie die Operatoren flatMap / switchMap. this.serviceA.get().flatMap((res1: any) => this.serviceB.get()).flatMap((res2: any) => this.serviceC.get()).subscribe( (res3: any) => { .... });

    – Pac0

    1. April 2018 um 9:30 Uhr


  • Meine Gedanken zu Promises & Observables sind Werkzeuge zum Erreichen verschiedener Ziele und keine Alternative zueinander. Z.B. Promises eignen sich hervorragend für die Verarbeitung einer Reihe von Aktionen, die ausgeführt werden müssen. Observables eignen sich hervorragend, wenn Sie möchten, dass mehrere Abonnenten benachrichtigt werden. Und natürlich macht es absolut Sinn, sie angemessen zu mischen.

    – Keith

    1. April 2018 um 9:35 Uhr


  • IHMO sollte überhaupt keine Observables für einmalige Aktionen und einen einzelnen Abonnenten verwenden. Genau dafür sind Versprechungen mit async/await da (und sie sind VIEL besser lesbar).

    – Spyro

    6. Oktober 2021 um 16:06 Uhr

Benutzer-Avatar
Pac0

Observables in Folge verketten, wie Sie es in Ihrem Code tun möchten

Wenn Sie in Bezug auf Ihr Codebeispiel Observables verketten (ein weiteres nach den vorherigen Emits auslösen) möchten, verwenden Sie flatMap (oder switchMap) für diesen Zweck :

this.serviceA.get()
  .flatMap((res1: any) => this.serviceB.get())
  .flatMap((res2: any) => this.serviceC.get())
  .subscribe( (res3: any) => { 
    .... 
  });

Dies ist eine bessere Übung im Vergleich zum Verschachteln, da dies die Dinge klarer macht und Ihnen hilft, die Callback-Hölle zu vermeiden, die Observable und Promises eigentlich verhindern sollten.

Erwägen Sie auch die Verwendung switchMap Anstatt von flatMap, erlaubt es im Grunde, die anderen Anfragen zu “stornieren”, wenn die erste einen neuen Wert ausgibt. Schön zu verwenden, wenn das erste Observable, das den Rest auslöst, zum Beispiel ein Klickereignis auf einer Schaltfläche ist.

Wenn Sie Ihre verschiedenen Anforderungen nicht der Reihe nach aufeinander warten müssen, können Sie verwenden forkJoin oder zip Um sie alle auf einmal zu starten, siehe die Antworten von @Dan Macak für Details und andere Einblicke.


Angular ‘async’ pipe und Observables arbeiten gut zusammen

In Bezug auf Observables und Angular können Sie perfekt verwenden | async in eine Angular-Vorlage leiten, anstatt das Observable in Ihrem Komponentencode zu abonnieren, um die von diesem Observable ausgegebenen Werte zu erhalten


ES6 async/await und Promises statt Observables?

Wenn Sie Observable nicht direkt verwenden möchten, können Sie einfach verwenden .toPromise() auf Ihrem Observable und dann einige async/await-Anweisungen.

Wenn Ihr Observable nur ein Ergebnis zurückgeben soll (wie es bei einfachen API-Aufrufen der Fall ist), kann ein Observable als durchaus äquivalent zu einem Promise angesehen werden.

Ich bin mir jedoch nicht sicher, ob dies notwendig ist, wenn man bedenkt, was Observable bereits bietet (An die Leser: Aufschlussreiche Gegenbeispiele sind willkommen!) . Ich wäre eher dafür, Observables wann immer möglich als Trainingsübung zu verwenden.


Einige interessante Blogartikel dazu (und es gibt noch viele andere):

https://medium.com/@benlesh/rxjs-observable-interop-with-promises-and-async-await-bebb05306875

Die toPromise-Funktion ist tatsächlich etwas knifflig, da es sich nicht wirklich um einen „Operator“ handelt, sondern um ein RxJS-spezifisches Mittel, um ein Observable zu abonnieren und es in ein Versprechen zu verpacken. Das Promise wird zum zuletzt ausgegebenen Wert des Observable aufgelöst, sobald das Observable abgeschlossen ist. Das bedeutet, wenn das Observable den Wert „hi“ ausgibt und dann 10 Sekunden wartet, bevor es abgeschlossen ist, wartet das zurückgegebene Versprechen 10 Sekunden, bevor es „hi“ auflöst. Wenn das Observable niemals abgeschlossen wird, löst sich das Promise niemals auf.

HINWEIS: Die Verwendung von toPromise() ist ein Antimuster, außer in Fällen, in denen Sie es mit einer API zu tun haben, die ein Promise erwartet, wie z. B. async-await

(Hervorhebung von mir)


Das von Ihnen angeforderte Beispiel

Übrigens, es wäre nett, wenn mir jemand einen Beispielcode geben könnte, um dies mit async/await zu lösen: D

Beispiel, wenn Sie Ja wirklich möchte es tun (wahrscheinlich mit einigen Fehlern, kann es jetzt nicht überprüfen, bitte zögern Sie nicht, es zu korrigieren)

// Warning, probable anti-pattern below
async myFunction() {
    const res1 = await this.serviceA.get().toPromise();
    const res2 = await this.serviceB.get().toPromise();
    const res3 = await this.serviceC.get().toPromise();
    // other stuff with results
}

Falls Sie alle Anfragen gleichzeitig starten können, await Promise.all() was effizienter sein sollte, da keiner der Aufrufe vom Ergebnis des anderen abhängt. (als würde forkJoin tun mit Observables)

async myFunction() {
    const promise1 = this.serviceA.get().toPromise();
    const promise2 = this.serviceB.get().toPromise();
    const promise3 = this.serviceC.get().toPromise();

    let res = await Promise.all([promise1, promise2, promise3]);

    // here you can retrieve promises results,
    // in res[0], res[1], res[2] respectively.
}

  • Eigentlich möchte ich nur das Versprechen verwenden, das von Http in @angular/http bereitgestellt wird, aber dieses Modul unterstützt keinen Interceptor (weil ich JWT verwende), also muss ich @angular/common/http verwenden, das Observable zurückgibt. Es verursacht mein Problem.

    – Thach Huynh

    1. April 2018 um 10:10 Uhr

  • Es wäre schön, wenn jemand Beispiele für Observable zip und forkJoin hinzufügen würde. Es kann verwendet werden, wenn Anforderungen parallel ausgeführt werden können, z. B. benötigen verschachtelte Abonnements keine Daten aus vorherigen Anforderungen.

    – Vayrex

    1. April 2018 um 10:29 Uhr

  • Über Ihr letztes Code-Snippet, eigentlich wäre es besser zu verwenden await Promise.all() mit all diesen Anrufen, sonst müssten sie auf vorherige warten, bevor sie die Anfrage überhaupt auslösen.

    – Skocdopole

    1. April 2018 um 10:29 Uhr

  • forkJoin und zip sind gute Ideen, ersteres umso mehr, da es nur die letzten Werte der inneren Observables ausgibt. Darauf werde ich in einer Antwort näher eingehen.

    – Skocdopole

    1. April 2018 um 10:35 Uhr


  • @Pac0 Grats für die tolle Antwort. Ich würde nur Ihr letztes Beispiel mit dem Abrufen von Werten aus vervollständigen Promise.allverwenden let res = await Promise.all(...) hinein res[0] / res[1] / res[2].

    – Jeto

    1. April 2018 um 11:55 Uhr


Benutzer-Avatar
Skocdopole

Da @Pac0 die verschiedenen Lösungen bereits gut ausgearbeitet hat, werde ich nur einen etwas anderen Winkel hinzufügen.

Versprechen und Observables mischen

Ich persönlich bevorzuge Versprechen und Observables nicht vermischen – was Sie erhalten, wenn Sie async await mit Observables verwenden, denn obwohl sie ähnlich aussehen, sind sie sehr unterschiedlich.

  • Promises sind immer asynchron, Observables nicht unbedingt
  • Promises repräsentieren nur 1 Wert, Observables 0, 1 oder viele
  • Promises haben nur sehr begrenzten Nutzen, man kann zB nicht. stornieren Sie sie (legen Sie ES als nächstes Proposal beiseite), Observables sind so viel mächtiger in ihrer Verwendung (Sie können beispielsweise mehrere WS-Verbindungen mit ihnen verwalten, versuchen Sie das mit Promises)
  • Ihre APIs unterscheiden sich stark

Verwendung von Promises in Angular

Nun, obwohl es manchmal gültig ist, beide zu verwenden, besonders bei Angular denke ich sollte man überlegen mit RxJS so weit wie möglich zu gehen. Die Gründe sind:

  • Ein großer Teil der Angular-API verwendet Observables (Router, http …), also geht man irgendwie mit und nicht gegen den Strom (kein Wortspiel beabsichtigt), indem man RxJS verwendet, sonst müsste man die ganze Zeit zu Promises konvertieren, während man die verlorenen Möglichkeiten, die RxJS bietet, nachholen muss
  • Angular hat mächtig async Pipe, mit der Sie Ihren gesamten Anwendungsdatenfluss aus Streams zusammenstellen können, die Sie filtern, kombinieren und beliebige Änderungen daran vornehmen, ohne den vom Server kommenden Datenstrom zu unterbrechen ohne eine einzige Notwendigkeit für thening oder Abonnement. Auf diese Weise müssen Sie die Daten nicht entpacken oder einigen Hilfsvariablen zuweisen, die Daten fließen einfach von den Diensten über Observables direkt in die Vorlage, was einfach schön ist.

Es gibt jedoch einige Fälle, in denen Versprechen kann immer noch glänzen. Zum Beispiel, was mir fehlt rxjs TypeScript-Typen sind ein Konzept von Single. Wenn Sie eine API erstellen, die von anderen verwendet werden soll, ist die Rückgabe von Observable nicht allzu aussagekräftig: Erhalten Sie 1 Wert, viele oder wird es nur abgeschlossen? Sie müssen einen Kommentar schreiben, um es zu erklären. Auf der anderen Seite hat Promise in diesem Fall einen viel klareren Vertrag. Es wird immer mit 1 Wert aufgelöst oder mit Fehler abgelehnt (es sei denn, es hängt natürlich für immer).

Im Allgemeinen müssen Sie definitiv nicht nur Promises oder nur Observables in Ihrem Projekt haben. Wenn man einfach mit einem Wert etwas ausdrücken möchte abgeschlossen wurde (Benutzer löschen, Benutzer aktualisieren) und Sie darauf reagieren möchten, ohne ihn in einen Stream zu integrieren, ist Promise der natürlichere Weg, dies zu tun. Auch mit async/await gibt Ihnen die Möglichkeit, Code sequenziell zu schreiben und vereinfacht ihn dadurch erheblich. Wenn Sie also keine erweiterte Verwaltung eingehender Werte benötigen, können Sie bei Promise bleiben.


Zurück zu deinem Beispiel

Meine Empfehlung lautet daher Nutzen Sie die Leistungsfähigkeit von RxJS und Angular. Um auf Ihr Beispiel zurückzukommen, können Sie den Code wie folgt schreiben (Credits für die Idee an @Vayrex):

this.result$ = Observable.forkJoin(
  this.serviceA.get(),
  this.serviceB.get(),
  this.serviceC.get()
);

this.result$.subscribe(([resA, resB, resC]) => ...)

Dieses Stück Code löst 3 Anfragen aus und sobald alle diese Anfragen von Observables abgeschlossen sind, Abonnement-Rückruf an forkJoin erhalten Sie die Ergebnisse in einem Array, und wie gesagt, Sie können es manuell abonnieren (wie im Beispiel) oder dies deklarativ mit tun result$ und async Rohr in der Schablone.

Verwenden Observable.zip würden Sie hier das gleiche Ergebnis erhalten, der Unterschied zwischen forkJoin und zip ist, dass erstere nur letzte Werte der inneren Observablen ausgibt, letztere kombiniert erste Werte der inneren Observables, dann zweite Werte usw.


Bearbeiten: Da Sie die Ergebnisse früherer HTTP-Anforderungen benötigen, verwenden Sie flatMap Ansatz in der Antwort von @ Pac0.

  • Eigentlich muss ich resA für den Aufruf von serviceB.get() verwenden. Was ist der beste Weg, dies mit Observable zu tun?

    – Thach Huynh

    1. April 2018 um 11:42 Uhr

  • Es ist der flatMap Vorgehen wie im Pac0s-Beispiel (das erste Snippet).

    – Skocdopole

    1. April 2018 um 11:45 Uhr

  • Zweiter Parameter zu subscribe ist ein Fehlerrückruf, oder wenn Sie sich nicht explizit anmelden oder mehr Freiheit bei der Fehlerbehandlung haben möchten, können Sie anrufen catch nach forkJoin mit Error-Handler als erstem Parameter. Dort können Sie Fehler beispielsweise einem anderen Wert zuordnen.

    – Skocdopole

    1. April 2018 um 15:23 Uhr

  • source$.switchMap(x => versprechenVerzögerung(x)) // funktioniert .subscribe(x => console.log(x));

    – Ashwin J

    2. August 2019 um 6:50 Uhr

Benutzer-Avatar
Chris Hamilton

Seit toPromise ist jetzt im Jahr 2022 veraltet. Ich möchte eine andere Verwendungsweise zeigen await auf einem beobachtbaren. Ich finde diese Methode, um viel besser lesbaren Code zu erstellen, im Gegensatz zu langen, komplexen rxjs-Pipes. Dies ist besonders nützlich für HTTP-Anfragen, da es nur eine Antwort gibt und Sie im Allgemeinen auf die Antwort warten möchten, bevor Sie etwas anderes tun.


Aktualisieren

Meine ursprüngliche Lösung funktioniert, aber rxjs hat im Grunde die gleiche Funktion: firstValueFrom().

Aus den Dokumenten:

async function execute() {
  const source$ = interval(2000);
  const firstNumber = await firstValueFrom(source$);
  console.log(`The first number is ${firstNumber}`);
}

Ursprüngliche Lösung

Wenn Sie ein Observable haben, können Sie es in ein Versprechen einpacken, abonnieren und auflösen, wenn das Abonnement abgeschlossen ist.

getSomething(): Promise<any> {
    return new Promise((resolve, reject) => {
      this.http
        .get('www.myApi.com')
        .subscribe({
          next: (data) => resolve(data),
          error: (err) => reject(err),
        });
    });
  }

Jetzt können wir innerhalb von auf eine Antwort warten async Funktion

  async ngOnInit() {
    const data = await this.getSomething();
    //Do something with your data
  }

Jetzt können wir viele komplexe Operationen mit den Daten durchführen und sie werden für Leute, die keine rxjs-Experten sind, viel besser lesbar sein. Wenn Sie drei aufeinander folgende HTTP-Anforderungen hätten, die sich aufeinander verlassen, würde es so aussehen:

  async ngOnInit() {
    const first = await this.getFirst();
    const second = await this.getSecond(first);
    const third = await this.getThird(second);
  }

Benutzer-Avatar
Glaukus

Observables eignen sich hervorragend für Streams, zB: BehaviorSubject. Aber ein einzelner Aufruf für Daten (z. B. http.get()) Sie sind wahrscheinlich besser dran, wenn Sie den Dienst selbst asynchron aufrufen.

async getSomethingById(id: number): Promise<Something> {
    return await this.http.get<Something>(`api/things/${id}`).toPromise();
}

Dann kannst du es einfach so nennen:

async someFunc(): Promise {
    console.log(await getSomethingById(1));
}

RxJS ist sehr leistungsfähig, aber die Verwendung für einen einfachen API-Aufruf scheint ein extremer Overkill zu sein. Selbst wenn Sie die abgerufenen Daten massieren müssen, können Sie dennoch die RxJS-Operatoren in der getSomethingById-Funktion verwenden und einfach das Endergebnis zurückgeben.

Der klare Vorteil von async/await ist, dass es klarer zu lesen ist und Sie nicht durch Reifen springen müssen, um Aufrufe zu verketten.

1142970cookie-checkIst es eine gute Praxis, Observable mit async/await zu verwenden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy