3
votes

Meilleur moyen pour plusieurs requêtes HTTP en angulaire

J'essaye d'envoyer 2 requêtes HTTP une par une; si la première réussit, envoyer la seconde, sinon afficher le message d'erreur correspondant à la première requête.

Je prévois d'utiliser quelque chose comme ça, mais je ne sais pas si c'est la meilleure option pour ce scénario:

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: 'app/app.component.html'
})
export class AppComponent {
  loadedCharacter: {};
  constructor(private http: HttpClient) {}

  ngOnInit() {
    this.http.get('/api/people/1').subscribe(character => {
      this.http.get(character.homeworld).subscribe(homeworld => {
        character.homeworld = homeworld;
        this.loadedCharacter = character;
      });
    });
  }
}

J'ai différentes demandes, par exemple PUT et CREATE en utilisant également cette approche. Je sais qu'il existe d'autres moyens, par exemple forkjoin , mergemap , mais si celui-ci résout mon problème semble être plus lisible. Une idée?


3 commentaires

Cela peut vous donner de l'inspiration


@Picci Semble être très utile, je vais certainement lire. Mais pour l'instant, j'ai besoin d'une solution urgente. Une suggestion pls?


Voici une courte réponse que j'ai écrite sur la gestion des observables imbriqués.


4 Réponses :


3
votes

2 abonnements imbriqués ne sont jamais une solution. Je recommande cette approche:

this.http.get('/api/people/1').pipe(
  switchMap(character => this.http.get(character.homeworld).pipe(
    map(homeworld => ({ ...character, homeworld })),
    // catchError(err => of({ ...character, homeworld: dummyHomeworld })),
  )),
  switchMap(character => this.http.get(character.university).pipe(
    map(university => ({ ...character, university})),
  )),
).subscribe(character => this.loadedCharacter = character);

Edit: Pour votre université

this.http.get('/api/people/1').pipe(
  switchMap(character => this.http.get(character.university).pipe(
    map(university => ({ ...character, university})),
  )),
).subscribe(character => this.loadedCharacter = character);

Ou même les demandes des universités et du monde d'origine en chaîne

this.http.get('/api/people/1').pipe(
  switchMap(character => this.http.get(character.homeworld).pipe(
    map(homeworld => ({ ...character, homeworld })),
  )),
).subscribe(character => this.loadedCharacter = character);


6 commentaires

Pourriez-vous s'il vous plaît et une mise à jour de votre réponse pour ce scénario? Vous obtenez d'abord une personne comme ci-dessus, puis mettez à jour son nom d'université ou son nom en appelant une autre demande?


Thnaks beaucoup. Continue-t-il à exécuter le deuxième appel même s'il y a une erreur au premier? D'autre part, comment puis-je gérer les erreurs pour chacun de ces appels?


Soit dit en passant, merci pour votre aide. Je serais reconnaissant si vous expliquiez un peu comment cela fonctionne également. Cordialement.


Avec cette chaîne, le deuxième appel ne serait pas exécuté si le premier échoue. Mais vous pouvez ajouter un catchError après l'opérateur de la map , j'ai à nouveau mis à jour ma réponse, j'ai seulement ajouté un exemple en commentaire. Avec catchError le flux externe continuerait.


Je suis vraiment désolé, mais j'ai besoin de quelques éclaircissements. En fait, vous envoyez des requêtes get et ajoutez le homeworld d' homeworld renvoyé au tableau de character . Est-ce vrai? Mais en fait, je veux envoyer une première demande d'ajout, puis en obtenant son identifiant, envoyer une autre demande pour ajouter un autre enregistrement (supposons que vous ajoutez une personne, puis ajoutez automatiquement les informations d'éducation par défaut en tant qu'école primaire). Donc, si cela ne vous dérange pas, pourriez-vous ajouter un troisième scénario pour cela? Le second serait également utile pour quelqu'un d'autre et il est bon de le conserver. Je pense que votre approche est très utile et c'est pourquoi je veux l'utiliser :) salutations.


Si vous avez le temps et ajoutez une mise à jour, je serais reconnaissant et j'utiliserai cette approche.



0
votes

Vous pouvez vérifier si la première demande a réussi ou non en vérifiant le code d'état:

  ngOnInit() {
    this.http.get('/api/people/1').subscribe((character: HttpResponse<any>) => {
      // here you should look for the correct status code to check, in this example it's 200
      if (character.status === 200) {
        this.http.get(character.homeworld).subscribe(homeworld => {
          character.homeworld = homeworld;
          this.loadedCharacter = character;
        });
      } else {
        // character is gonna contain the error
        console.log(character)
      }
    });
  }


2 commentaires

Les subscribe imbriqués ne sont pas considérés comme idiomatiques avec les RxJ


Merci pour votre aide, mais je pense que cette approche n'est pas suggérée.



8
votes

Tout d'abord, votre code fonctionne et c'est génial - vous pouvez le laisser tel quel et tout ira bien.

D'autre part, il existe un moyen pour de multiples améliorations qui vous aideront, vous et vos collègues à l'avenir:

  1. essayez de déplacer la logique liée à http vers le service au lieu d'appeler http dans les composants - cela vous aidera à diviser le code en logique liée à la vue et en logique métier / récupération / transformation.
  2. essayez d'éviter les subscribe imbriqués - non seulement vous ignorez la puissance des Observable mais vous liez également le code à un certain flux sans pouvoir réutiliser ces lignes quelque part dans l'application. Le retour de l' Observable peut vous aider à «partager» les résultats de la requête ou à la transformer d'une manière ou d'une autre.
  3. flatMap/mergeMap , concatMap et switchMap fonctionnent différemment, vous offrant la possibilité de contrôler le comportement comme vous le souhaitez. Cependant, pour http.get() ils fonctionnent presque de manière similaire, c'est une bonne idée de commencer à apprendre ces opérateurs combinant dès que possible.
  4. pensez à la façon dont vous allez gérer les erreurs dans ce cas - que se passera-t-il si votre premier appel aboutit à une erreur? Observable ont un mécanisme puissant pour les gérer, tandis que .subscribe vous permet de gérer une erreur d'une seule manière.

Un exemple utilisant le switchMap :

import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-root',
  templateUrl: 'app/app.component.html'
})
export class AppComponent {
  loadedCharacter: {};
  constructor(private http: HttpClient) {}

  ngOnInit() {
    const character$ = this.http.get('/api/people/1').pipe(
      tap(character => this.characterWithoutHomeworld = character), // setting some "in-between" variable
      switchMap(character => {
        return this.http.get(character.homeworld).pipe(
            map(homeworld => {
                    return {
                        ...character,
                        homeworld: homeworld
                    }
                }
            )
        )
      }),
      catchError(errorForFirstOrSecondCall => {
        console.error('An error occurred: ', errorForFirstOrSecondCall);
        // if you want to handle this error and return some empty data use:
        // return of({});
        
        // otherwise: 
        throw new Error('Error: ' + errorForFirstOrSecondCall.message);
      })
);

    // you can either store this variable as `this.character$` or immediately subscribe to it like:
    character$.subscribe(loadedCharacter => {
        this.loadedCharacter = loadedCharacter;
    }, errorForFirstOrSecondCall => {
       console.error('An error occurred: ', errorForFirstOrSecondCall);
    })
  }
}



11 commentaires

Merci beaucoup pour vos explications détaillées. En fait, c'est pourquoi j'ai posé cette question même si cela fonctionnait. Donc, dans ce cas, pourriez-vous publier le code ci-dessus en utilisant l'une de vos suggestions, par exemple flatMap/mergeMap etc.? Je préférerais une manière la plus lisible afin de pouvoir manipuler facilement les résultats.


Vous obtenez un maximum de votes positifs, mais il serait préférable de suggérer une utilisation. Merci d'avance.


@PragDopravy J'ai ajouté un exemple avec switchMap


Désolé, mais je ne suis pas sûr d'avoir très bien expliqué le scénario. Supposons que j'ai 2 appels API différents pour la mise à jour des données et; 1. Dans chaque appel, je veux obtenir la réponse de cet appel et attraper l'erreur liée à cet appel. 2. Si le premier appel échoue, je veux interrompre et n'exécute pas le deuxième appel après avoir obtenu une erreur ou vérifié les données de réponse. Donc, je pense qu'il semble préférable d'utiliser switchMap comme vous l'avez donné. Alors, pourriez-vous mettre à jour votre réponse en fonction du scénario que j'ai expliqué ici?


Le scénario ci-dessus avec échec lors d'une erreur de premier appel ou d'une deuxième erreur d'appel. Les erreurs peuvent être .subscribe soit dans le .subscribe de .subscribe , soit à la toute fin du .pipe du character$ ( catchError ). Le deuxième appel ne sera pas effectué si le premier entraîne une erreur. Si vous souhaitez définir certaines données du premier appel, déplacez-les simplement vers une variable distincte ou utilisez .tap(data => this.data = data) (mais à partir de votre exemple, vous ignorez presque complètement la réponse du premier appel)


Pourriez-vous mettre à jour la réponse concernant le scénario ci-dessus? Si c'est possible, j'aimerais utiliser switchMap dans cet exemple.


Fait - switchMap , in-between de stockage variable et catchError sont présents dans l'exemple


Thnanks pour une explication détaillée. 1. Autant que je vois, un bloc catch serait suffisant pour plusieurs appels ou blocs switchMap . Est-ce vrai? 2. D'autre part, que faire si je dois passer plus d'appels? Dans ce cas, dois-je ajouter plusieurs blocs switchMap juste en dessous de celui de votre code? 3. Que dois-je utiliser pour terminer le flux? Il y a des return NEVER , des return EMPTY , etc., mais je n'ai aucune idée de celui que je dois faire?


Oui, catchError toutes les erreurs s'il est placé à la fin. Plus d'appels - plus de switchMaps , c'est également vrai (copiez simplement le premier). Vous n'avez pas besoin de terminer le flux - il est automatiquement terminé par le http de Angular


Merci, mais si je veux terminer le flux à tout moment, par exemple en appliquant une logique ou si le résultat de l'appel n'est pas adapté à la condition, je pense que je devrais finaliser. Sinon, le résultat passera au flux suivant ( switchMap suivant). Est-ce vrai? la seconde question est quand je n'utilise return NEVER ou return serv, ce.method (..) `ça donne une erreur. Alors, dois-je utiliser l'un d'entre eux dans le switchMap ?


Essayez de préciser vos scénarios plus clairement - des étapes claires et ce que vous ne voulez pas accomplir. La chaîne de pipe-able les opérateurs peuvent résoudre toute tâche liée au flux alors que certains if l ' intérieur switchMap peuvent contrôler le flux de votre logique. À partir de l'exemple ci-dessus - il n'y a pas de moyen correct de finalising le flux, vous devriez penser à les diviser en plusieurs variables et à les combiner comme vous le souhaitez. De plus, votre switchMap peut renvoyer soit this.http.get soit of(null) si vous ne souhaitez pas effectuer d'appels http en fonction de la condition.



2
votes

Vous pouvez essayer une solution en utilisant switchmap et forkJoin pour Enchaînement plus facile et la gestion des erreurs. cela aidera à garder le code propre au cas où la chaîne continuerait à se développer dans un nid profond.

this.http
      .get("/api/people/1")
      .pipe(
        catchError((err) => {
          console.log("e1", err);
        }),
        switchMap((character) => {
          return forkJoin({
            character: of(character),
            homeworld: this.http.get(character.homeworld).pipe(
              catchError((err) => {
                console.log("e2", err);
              })
            )
          });
        })
      )
      .subscribe(({ character, homeworld }) => {
        character.homeworld = homeworld;
        this.loadedCharacter = character;
      });

EDIT: Scénario 2

    this.http
      .get("/api/people/1'")
      .pipe(
        catchError((err) => {
          // handle error
        }),
        switchMap((character) => {
          return forkJoin({
            character: of(character),
            homeworld: this.http.get(character.homeworld)
          });
        })
      )
      .subscribe(({ character, homeworld }) => {
        character.homeworld = homeworld;
        this.loadedCharacter = character;
      });

Vous pouvez enchaîner une erreur de capture ou ajouter une fonction distincte pour la gestion des erreurs sans qu'elle appelle le prochain appel d'API. mais je recommanderais d'abstraire la logique du backend à un service angulaire et d'utiliser cette méthode. ce qui aiderait à conserver une structure facile à lire.


4 commentaires

Semble être bon au lieu d'utiliser des imbriqués. Merci, voté.


Pourriez-vous également s'il vous plaît ajouter une autre utilisation avec cette approche comme suit? Lors de la première demande de mise à jour d'un enregistrement, puis en fonction de la valeur renvoyée (par exemple, le code d'état), envoyer une autre demande d'ajout d'un autre enregistrement et d'un message d'affichage?


donc vous ne voulez envoyer la deuxième demande que si le code d'état est disons 200?


Pas vraiment, je veux dire qu'au premier appel, ajoutez un enregistrement, puis envoyez une autre demande de mise à jour d'un autre enregistrement. Mais pendant ce temps, je veux gérer les erreurs pour chaque demande et s'il y a une erreur dans la première demande, je veux casser et afficher simplement un message d'erreur sans exécuter la seconde.