it-swarm.com.de

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

Ich verwende angular 2 gemeinsame http, die ein Observable zurückgeben, aber ich habe das Problem, dass mein Code ein Mesh mag, wenn ich 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 wäre es nett, wenn mir jemand einen Beispielcode zur Lösung dieses Problems mit async/await geben könnte: D

25
Thach Huynh

Verketten von Observables nacheinander, wie Sie es in Ihrem Code tun möchten

Wenn Sie in Ihrem Codebeispiel Observables verketten möchten (nach den vorherigen Ausgaben erneut auslösen), 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 Methode als das Verschachteln, da dies die Dinge klarer macht und Ihnen hilft, die Rückruf-Hölle zu vermeiden, die Observable and Promises eigentlich verhindern sollte.

Erwägen Sie auch die Verwendung von switchMap anstelle von flatMap. Grundsätzlich können die anderen Anforderungen abgebrochen werden, wenn die erste einen neuen Wert ausgibt. Schön zu verwenden, wenn das erste Observable, das den Rest auslöst, beispielsweise ein Klickereignis auf eine Schaltfläche ist.

Wenn Sie nicht benötigen, dass Ihre verschiedenen Anforderungen aufeinander warten, können Sie forkJoin oder Zip verwenden, um sie alle auf einmal zu starten, siehe @ Dan Macak answer's für Details und andere Einsichten.


Angular 'async' pipe und Observables arbeiten gut zusammen

In Bezug auf Observables und Angular können Sie die Pipe | async In einer Vorlage Angular) verwenden, anstatt die Observable in Ihrem Komponentencode zu abonnieren, um die von dieser Vorlage ausgegebenen Werte abzurufen Beobachtbar


ES6 async/await und Promises statt Observables?

wenn Sie nicht das Gefühl haben, Observable direkt zu verwenden, können Sie einfach .toPromise() auf Ihrem Observable verwenden und anschließend einige asynchrone/wait-Anweisungen ausführen.

Wenn Ihr Observable nur ein Ergebnis zurückgeben soll (wie es bei grundlegenden API-Aufrufen der Fall ist), kann ein Observable als ziemlich gleichwertig mit einem Promise angesehen werden.

Ich bin mir jedoch nicht sicher, ob dies erforderlich ist, wenn man bedenkt, was Observable bereits bietet (Für Leser: Aufschlussreiche Gegenbeispiele sind willkommen!). Ich würde Observables als Trainingsübung bevorzugen, wann immer Sie können.


Einige interessante Blog-Artikel dazu (und es gibt viele andere):

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

Die toPromise-Funktion ist eigentlich 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 hüllen. Das Versprechen wird in den zuletzt ausgegebenen Wert des Observablen aufgelöst, sobald das Observable abgeschlossen ist . Das heißt, wenn der Observable den Wert "hi" ausgibt und dann 10 Sekunden wartet, bevor er abgeschlossen ist, wartet das zurückgegebene Versprechen 10 Sekunden, bevor "hi" aufgelöst wird. Wenn das Observable niemals abgeschlossen wird, wird das Versprechen niemals aufgelöst.

HINWEIS: Die Verwendung von toPromise () ist ein Antipattern, es sei denn, Sie haben es mit einer API zu tun, die ein Promise erwartet, z. B. async-await

(Hervorhebung von mir)


Das von Ihnen angeforderte Beispiel

Übrigens wäre es nett, wenn mir jemand einen Beispielcode zur Lösung dieses Problems mit async/await geben könnte: D

Beispiel, wenn Sie wirklich möchten (wahrscheinlich mit einigen Fehlern, kann jetzt nicht überprüfen, bitte zögern Sie nicht 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
}

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

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 promises results,
    // res[0], res[1], res[2] respectively.
}
41
Pac0

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

Versprechen und Observables mischen

Ich persönlich bevorzuge Versprechen und Observables nicht mischen - das ist es, was Sie erhalten, wenn Sie mit Observables asynchrone erwarten, denn obwohl sie ähnlich aussehen, sind sie sehr unterschiedlich.

  • Versprechen sind immer asynchron, Observables nicht unbedingt
  • Versprechen stellen nur 1 Wert dar, Observables 0, 1 oder viele
  • Versprechen haben nur eine sehr begrenzte Verwendung, Sie können nicht z. storniere sie (lege die nächsten Vorschläge beiseite), Observables sind so viel leistungsfähiger in ihrer Verwendung (du kannst zum Beispiel mehrere WS-Verbindungen mit ihnen verwalten, versuche das mit Promises)
  • Ihre APIs unterscheiden sich stark

Verwendung von Versprechungen im Winkel

Jetzt, obwohl es manchmal gültig ist, beide zu verwenden, insbesondere mit Angular Ich denke, man sollte überlegen, mit RxJS so weit wie möglich zu gehen. Die Gründe dafür sind:

  • Großer Teil von Angular API verwendet Observables (Router, http ...), so dass eine Art mit und nicht gegen den Stream geht (kein Wortspiel beabsichtigt) Wenn Sie RxJS verwenden, müssen Sie andernfalls die ganze Zeit auf Promises umstellen, während Sie die verlorenen Möglichkeiten ausgleichen, die RxJS bietet
  • Angular verfügt über eine leistungsstarke async -Pipe, mit der Sie den gesamten Anwendungsdatenfluss aus Streams zusammenstellen können, die Sie filtern, kombinieren und nach Belieben ändern können, ohne den vom Server kommenden Datenstrom zu unterbrechen ohne einen einzigen brauchen dann oder abonnieren. 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, die einfach nur schön ist.

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

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


Zurück zu deinem Beispiel

Also meine Empfehlung ist es, sowohl die Macht von RxJS als auch von Angula anzunehmen r. Um auf Ihr Beispiel zurückzukommen, können Sie den Code wie folgt schreiben (Dank 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]) => ...)

Dieser Code löst 3 Anforderungen aus. Sobald alle Anforderungen von Observables abgeschlossen sind, erhalten Sie durch einen Abonnementrückruf an forkJoin die Ergebnisse in einem Array Beispiel) oder deklarativ mit result$ und async Pipe in der Vorlage.

Mit Observable.Zip würde hier zu demselben Ergebnis führen, der Unterschied zwischen forkJoin und Zip besteht darin, dass erstere nur die letzten Werte der inneren Observablen ausgibt, letztere die ersten Werte der inneren Observablen kombiniert und dann die zweiten Werte etc.


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

12
Dan Macák