web-dev-qa-db-fra.com

Est-ce une bonne pratique d’utiliser Observable avec async / wait?

J'utilise angular 2 http communes qui renvoient un observable, mais je suis confronté à un problème: mon code aime un maillage lorsque j'utilise un appel imbriqué Observable:

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

       })
   })
})

Maintenant, je veux utiliser async/wait pour éviter cela, mais async/wait ne fonctionne qu'avec Promise. Je sais qu’Observable peut être converti en Promesse, mais comme je le sais, ce n’est pas une bonne pratique. Alors que dois-je faire ici?

BTW, ce sera bien si quelqu'un peut me donner un exemple de code pour résoudre ce problème avec async/wait: D

25
Thach Huynh

Chaînage des observables en séquence, comme vous voulez le faire dans votre code

En ce qui concerne votre exemple de code, si vous souhaitez enchaîner des observables (en déclencher un autre après les précédentes émissions), utilisez flatMap (ou switchMap) à cette fin:

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

Celui-ci est une meilleure pratique par rapport à l'imbrication, car cela clarifiera les choses et vous aidera à éviter les rappels, que Observable et Promises étaient censés aider à prévenir en premier lieu.

Pensez également à utiliser switchMap au lieu de flatMap. En gros, cela permettra d’annuler les autres requêtes si la première émet une nouvelle valeur. C'est pratique à utiliser si le premier observable qui déclenche le reste est un événement de clic sur un bouton, par exemple.

Si vous n'avez pas besoin d'attendre vos différentes requêtes les unes après les autres, vous pouvez utiliser forkJoin ou Zip pour les démarrer toutes en même temps, voir Réponses @ Dan Macak pour plus de détails et d’autres idées.


Pipe angulaire 'asynchrone' et observables fonctionnent bien ensemble

Concernant Observables et Angular, vous pouvez parfaitement utiliser le tube | async Dans un template Angular au lieu de vous abonner à l’observable dans le code de votre composant, pour obtenir la ou les valeurs émises par celui-ci. Observable


ES6 async/wait et Promises au lieu de Observables?

si vous ne vous sentez pas utiliser Observable directement, vous pouvez simplement utiliser .toPromise() sur votre observable, puis quelques instructions asynchrones/wait.

Si votre observable est censé renvoyer un seul résultat (comme c'est le cas pour les appels d'API de base), un observable peut être considéré comme tout à fait équivalent à une promesse.

Cependant, je ne suis pas sûr qu'il soit nécessaire de le faire, compte tenu de tout ce que Observable fournit déjà (aux lecteurs: des contre-exemples éclairants sont les bienvenus!). Je serais plus favorable à l'utilisation d'observables chaque fois que vous le pouvez, comme exercice d'entraînement.


Quelques articles de blog intéressants à ce sujet (et il y en a beaucoup d'autres):

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

La fonction toPromise est en réalité un peu délicate, car ce n'est pas vraiment un "opérateur", mais plutôt un moyen spécifique à RxJS de s'abonner à un observable et de l'envelopper dans une promesse. La promesse résoudra à la dernière valeur émise de l'observable une fois que celui-ci sera terminé . Cela signifie que si l'Observable émet la valeur "hi" puis attend 10 secondes avant d'être terminé, la promesse retournée attendra 10 secondes avant de résoudre "hi". Si l'Observable ne se termine jamais, la promesse ne se résout jamais.

REMARQUE: l’utilisation de toPromise () est un antipattern, sauf dans les cas où vous traitez avec une API qui attend une promesse, telle que async-wait

(c'est moi qui souligne)


L'exemple que vous avez demandé

BTW, ce sera bien si quelqu'un peut me donner un exemple de code pour résoudre ce problème avec async/wait: D

Exemple si vous vraiment voulez le faire (probablement avec quelques erreurs, vous ne pouvez pas vérifier pour le moment, n'hésitez pas à les corriger)

// 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
}

Dans le cas où vous pouvez lancer toutes les demandes simultanément, await Promise.all() devrait être plus efficace, car aucun des appels ne dépend du résultat de l'autre. (comme le ferait forkJoin avec 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 promises results,
    // res[0], res[1], res[2] respectively.
}
41
Pac0

Comme @ Pac0 a déjà expliqué les différentes solutions, je vais simplement ajouter un angle légèrement différent.

Mélange de promesses et d'observables

Personnellement, je préfère ne pas mélanger Promises et Observables - ce que vous obtenez lorsque vous utilisez une attente asynchrone avec Observables, car même si elles se ressemblent, elles sont très différentes.

  • Les promesses sont toujours asynchrones, les observables pas nécessairement
  • Les promesses représentent juste 1 valeur, les observables 0, 1 ou plusieurs
  • Les promesses ont un usage très limité, vous ne pouvez pas par exemple. annulez-les (mettez de côté les prochaines propositions ES), les observables sont tellement plus puissants dans leur utilisation (vous pouvez par exemple gérer plusieurs connexions WS avec eux, essayez cela avec Promises)
  • Leurs API diffèrent grandement

Utilisation de promesses en angulaire

Maintenant, même s’il est parfois valide d’utiliser les deux, en particulier avec Angular, je pense qu’il faut envisager d’aller aussi loin que possible avec RxJS. Les raisons étant:

  • Une grande partie de Angular API utilise Observables (routeur, http ...), donc un type de va avec et non contre le flux (sans jeu de mots) en utilisant RxJS, sinon il faudrait convertir tout le temps en Promises tout en rattrapant les possibilités perdues que RxJS offre
  • Angular a le puissant async pipe qui permet de composer l’ensemble de votre flux de données d’application que vous filtrez, combinez et effectuez les modifications souhaitées sans interrompre le flux de données provenant du serveur sans aucune besoin de s'inscrire ou de s'inscrire. De cette façon, vous n'avez pas besoin de dérouler les données ni de les affecter à des variables auxiliaires, elles passent simplement des services à travers Observables directement dans le modèle, ce qui est magnifique.

Il existe quelques cas où La promesse peut encore briller. Par exemple, ce qui me manque dans rxjs Les types TypeScript correspondent au concept de single . Si vous créez une API destinée à être utilisée par d'autres utilisateurs, renvoyer Observable n'est pas tout à fait révélateur: recevrez-vous une valeur, plusieurs, ou la compléterez-elle? Vous devez écrire un commentaire pour l'expliquer. En revanche, Promise a un contrat beaucoup plus clair dans ce cas. Il sera toujours résolu avec 1 valeur ou rejeté avec erreur (à moins qu'il ne soit bloqué pour toujours, bien sûr).

Généralement, vous n'avez certainement pas besoin d'avoir que des promesses ou des observables dans votre projet. Si vous voulez simplement exprimer avec une valeur que quelque chose a été complété (supprimer un utilisateur, mettre à jour un utilisateur), et que vous souhaitez réagir sans l'intégrer à Dans certains cas, Promise est la manière la plus naturelle de le faire. En outre, l'utilisation de async/await Vous permet d'écrire du code de manière séquentielle, ce qui le simplifie grandement. Ainsi, sauf si vous avez besoin d'une gestion avancée des valeurs entrantes, vous pouvez rester avec Promise.


Retour à votre exemple

Donc, ma recommandation est de embrasser à la fois le pouvoir de RxJS et Angula r. Pour revenir à votre exemple, vous pouvez écrire le code comme suit (crédits pour l'idée à @Vayrex):

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

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

Ce morceau de code déclenchera 3 requêtes et une fois que toutes les requêtes Observables auront été complétées, le rappel d'abonnement à forkJoin vous obtiendra les résultats dans un tableau. Vous pouvez vous y abonner manuellement (comme dans le exemple) ou faites ceci de manière déclarative en utilisant result$ et async pipe dans le modèle.

En utilisant Observable.Zip Vous obtiendriez le même résultat ici, la différence entre forkJoin et Zip est que le premier n’émet que les dernières valeurs des observables internes, le dernier combine les premières valeurs du Observables internes, puis secondes valeurs, etc.


Edit: Puisque vous avez besoin des résultats des requêtes HTTP précédentes, utilisez la méthode flatMap dans la réponse de @ Pac0.

12
Dan Macák