46 votes

Angular 6 / Rxjs - comment faire les bases : observables succès, erreur, finalement

Je suis en train de construire une architecture sur la dernière version d'Angular 6 et, venant d'AngularJS, il y a quelque chose que je n'arrive pas à accepter : le traitement de base d'une requête HTTP.

Donc, pour les besoins de la question, disons que je veux un observable. Parce que cela semble être l'avenir d'Angular.

Je suis passé de quelque chose de très élégant, en AngularJS :

   service.getAll()
    .then(onSuccess) // I process the data
    .catch(onError) // I do whatever needed to notify anyone about the issue
    .finally(onFinally); // I stop the loading spinner and other stuff

Maintenant, dans Angular 6/RxJS 6, je ne comprends pas pourquoi tout est si compliqué et ne semble pas correct.

Je pourrais trouver deux façons de faire la même chose que ci-dessus :

  1. Le tuyau plein

    this.service.getAll()
        .pipe(
            map((data) => this.onSuccess(data)),
            catchError(error => of(this.handleError(error))),
            finalize(() => this.stopLoading())
        )
        .subscribe();

Puisque nous devons utiliser pipe pour le finalize, je pourrais aussi bien utiliser pipe pour tout, je pense que c'est une meilleure pratique d'avoir tout dans le même ordre. Mais maintenant nous devons lancer quelque chose, appelé "of" (pas très facile à comprendre) et je n'aime pas ça.

  1. Le demi-tube J'essaie donc une autre idée, avec seulement le tuyau dont j'ai besoin (finalize) et je garde les callbacks d'abonnement.

    this.service.getAll()
    .pipe(
        finalize(() => this.stopLoading())
    )
    .subscribe(
        (data) => this.onSuccess(data),
        (error) => this.handleError(error)
    );

Mais, bon. N'est-ce pas un peu arriéré ? Nous avons toujours des callbacks sans noms réels, et nous finalisons avant de lire le traitement et l'erreur. Bizarre.

Il y a donc quelque chose que je ne comprends absolument pas. Et je ne trouve rien en ligne sur cette question fondamentale. Soit vous avez quelqu'un qui veut "succès et enfin", soit "succès et erreur" mais personne ne veut les 3. Peut-être suis-je trop vieux et ne comprends-je pas la nouvelle meilleure pratique à ce sujet (si c'est le cas, éduquez-moi !).

Mon besoin est simple :
1. Je veux traiter les données que je reçois d'un service
2. Je veux obtenir l'erreur afin de l'afficher à l'utilisateur.
3. Je veux arrêter le spinner de chargement que je viens de lancer avant l'appel, ou faire un autre appel une fois que le premier est terminé en cas de succès ou d'erreur (je veux vraiment un finally).

Comment gérer votre appel HTTP de base avec observable ?

(Je ne veux pas de .toPromise s'il vous plaît, je veux comprendre comment faire avec les nouvelles choses).

47voto

Martin Points 1093

Je pense qu'il y a un malentendu essentiel :

Vous avez soit quelqu'un qui veut "le succès et enfin", soit "le succès et l'erreur", mais aucun ne veut les 3.

Ce n'est pas tout à fait vrai. Chaque Observable peut envoyer zéro ou plusieurs next et une error o complete mais jamais les deux. Par exemple, lorsque vous effectuez un appel HTTP avec succès, vous aurez une next et un complete notification. Lors d'une requête HTTP d'erreur, vous n'aurez qu'une seule error notification et c'est tout. Voir http://reactivex.io/documentation/contract.html

Cela signifie que vous n'aurez jamais un Observable émettant à la fois error y complete .

Et puis il y a le finalize opérateur. Cet opérateur est invoqué lors de l'élimination de la chaîne (ce qui inclut également le désabonnement pur et simple). En d'autres termes, il est appelé après les deux error y complete notifications.

Donc le deuxième exemple que vous avez est correct. Je comprends que cela semble bizarre que vous incluiez finalize avant de s'abonner mais en fait, chaque émission de l'Observable source va d'abord de haut en bas jusqu'à ce qu'elle atteigne les abonnés et là, si son error o complete il déclenche des gestionnaires de disposition de bas en haut (dans l'ordre inverse) et à ce moment-là finalize est appelé. Voir https://github.com/ReactiveX/rxjs/blob/master/src/internal/Subscriber.ts#L150-L152

Dans votre exemple utilisant finalize est la même chose que d'ajouter vous-même le gestionnaire de disposition dans un fichier Subscription objets.

const subscription = this.service.getAll()
  .subscribe(
    (data) => this.onSuccess(data),
    (error) => this.handleError(error)
  );

subscription.add(() => this.stopLoading());

3 votes

C'est très proche de ce que j'avais en tête. Merci pour l'explication. J'ai juste le mot "add" au lieu de "finally" mais comme c'est dans le bon ordre de lecture, je pense que c'est correct. this.service.getAll().subscribe(success,error).add(finally);

1 votes

@SimonPeyou Je suis sur la même longueur d'onde que toi, le passage des Promesses aux Observables est si brutal à cause de ces étranges conventions d'appellation. Sur les requêtes HTTP, je préfère avoir des intercepteurs pour attraper les erreurs et laisser les promesses/observables échouer avec un finalement pour nettoyer les choses.

0 votes

Excellente explication, même s'il m'a fallu du temps pour trouver la description de add(), Ajoute un tear down à appeler lors du unsubscribe() de cet abonnement.

9voto

Picci Points 4743

El subscribe de l'Observable accepte 3 fonctions optionnelles comme paramètres

  • le premier à traiter les données qui viennent avec l'événement soulevé par l'Observable
  • le second pour traiter les erreurs éventuelles.
  • le troisième fait quelque chose à l'achèvement de l'Observable.

Donc, si je comprends bien, ce que vous voulez peut être réalisé avec un code qui ressemble à ceci

this.service.getAll()
.subscribe(
    data => this.onSuccess(data),
    error => this.handleError(error),
    () => this.onComplete()
);

Considérez que l'utilisation d'Observables pour les appels http peut donner des avantages lorsque vous voulez réessayer (voir retry ) au cas où vous auriez des conditions de course (via l'utilisation de l'opérateur switchMap ). Je pense que ce sont les principales raisons pour lesquelles l'équipe d'Angular a choisi cette approche pour le client http.

De manière générale, je pense qu'il vaut la peine de commencer à connaître le fonctionnement des Observables et certains des opérateurs les plus importants (en plus de ceux mentionnés ci-dessus, je pense d'abord à mergeMap , filter , reduce - mais il y en a beaucoup d'autres) est important car il peut simplifier considérablement de nombreuses tâches dans des environnements asynchrones non bloquants, tels que le navigateur (ou Node par exemple).

5 votes

Malheureusement, et corrigez-moi si j'ai tort, l'argument "complet" dans la version .subscribe est déclenchée lorsque tout s'est bien passé. Elle n'est pas déclenchée en cas d'erreur. Je comprends les avantages des Observables, je ne plaide pas contre. Je veux juste être sûr de la façon de procéder (et peut-être pourquoi ce n'est plus la bonne pratique de base).

0 votes

Oui, vous avez raison, soit complete o error ou rien, pour des Observables qui ne sont jamais complets. Mais je ne comprends pas la partie triste.

0 votes

Même commentaire que ci-dessus, et je commence à penser que je manque quelque chose d'évident à ce sujet : dans mon exemple, où est-ce que j'appelle le stopLoading alors ?

3voto

dAxx_ Points 1577

Je pense que la bonne méthode consiste à utiliser les fonctions Observable : next, err, complete.
Voici un exemple de la façon dont vous pouvez déclencher la fonction complète.
Disons que nous avons l'objet BehaviorSubject :

let arr = new BehaviorSubject<any>([1,2]);

Maintenant, supposons que nous voulons nous y abonner, et si nous obtenons la valeur "finish", nous voulons terminer.

let arrSubscription = arr.asObservable().subscribe(
  data => {
      console.log(data)
      if(data === 'finish') {
        arr.complete()
      }
  },
  err => {
      console.log(err)
  },
  () => {
      console.log("Complete function triggered.")
  }
);
arr.next([3,4])
arr.next('finish')
arr.next([5,6])

Le journal de la console est :

[1,2]
[3,4]
finish
Complete function triggered.

Puisque nous avons déclenché la fonction complete, le dernier .suivant notre BehaviorSubject ne sera pas déclenché, puisque err et complete function sont des terminateurs de l'abonnement.
Ceci est juste un exemple de la façon dont vous pouvez déclencher la fonction complète, à partir de là, vous pouvez faire ce que vous voulez.

2 votes

Je suis désolé, je ne vois pas comment cela fonctionne avec l'observable Angular HTTP. Si je suis dans l'erreur, je dois appeler complete aussi alors. Je pourrais aussi bien appeler mes fonctions postCompletion là.

0 votes

Non, error et complete sont des terminateurs de l'abonnement alors que data est infini. Si vous avez une erreur, vous devez gérer l'erreur, et ce n'est pas le même comportement que de gérer un complet.

0 votes

Dans mon exemple, où dois-je appeler le stopLoading ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X