Gestion des erreurs RXJS -NGRX

Dans un projet frontend, lorsqu’on fait un appel HTTP, il ne faut pas oublier la gestion des cas d’erreurs. Un appel HTTP peut être en erreur pour diverses raisons, on peut citer :

un serveur est inaccessible : le backend est “tombé” à cause d’une e…


This content originally appeared on DEV Community and was authored by Clara Belair

Dans un projet frontend, lorsqu'on fait un appel HTTP, il ne faut pas oublier la gestion des cas d'erreurs. Un appel HTTP peut être en erreur pour diverses raisons, on peut citer :

  • un serveur est inaccessible : le backend est "tombé" à cause d'une erreur interne par exemple
  • un timeout si la requête prend plus d'un certain temps à répondre
  • une erreur renvoyée par le backend avec un message spécifique : l'utilisateur n'a pas le droit d'accéder à cette ressource par exemple

Dans chaque cas, si le frontend ne gère pas ces erreurs, on se retrouve avec une application qui fonctionne mal ou, dans le pire des cas, plus du tout.

Dans cet article, je vais vous présenter la façon de gérer vos erreurs lors d'un appel HTTP pour un projet Angular. On verra d'abord la gestion des erreurs dans un subscribe, puis la gestion des erreurs dans un effect.

Prenons l'exemple d'un service HobbitsService et de la méthode findHobbits qui fait un appel HTTP pour retourner un observable d'une liste d'Hobbits.

@Injectable()
export class HobbitsService {
  constructor(private http: HttpClient) {}

  findHobbits(): Observable<Hobbit[]> {
    return this.http.get<Hobbit[]>('api/hobbits');
  }
}

On veut afficher la liste des Hobbits, et pendant que la requête HTTP est en cours, on affiche un loader à l'utilisateur.

Gérer les erreurs dans un subscribe

Exemple d'une erreur non traitée

Dans le composant HobbitsComponent une liste d'Hobbits est récupérée à l'initialisation du composant. Un loader est affiché lorsque le booléen isLoading est à true.

export class HobbitsComponent implements OnInit {

  isLoading = true;
  hobbits: Hobbit[] = [];

  constructor(private hobbitsService: HobbitsService) {}

  ngOnInit() {
    this.hobbitsService.findHobbits().subscribe(
      (hobbits: Hobbit[]) => {
        this.hobbits = hobbits;
        this.isLoading = false;
      }
    );
  }
}

Que se passe-t-il si l'appel findHobbits échoue ?

Le loader va être affiché, sans s'arrêter, alors que l'appel est terminé.

Pourquoi ?

La gestion du statut du loader est placé dans la fonction NEXT du subscribe. Quand une erreur survient, on ne passe pas dans NEXT mais dans la fonction ERROR du subscribe.

NEXT, ERROR, COMPLETE : les 3 fonctions d'un subscribe

subscribe a 3 fonctions optionnelles : NEXT, ERROR, COMPLETE.

this.hobbitsService.findHobbits().subscribe(
    () => console.log('Next'),
    () => console.log('Error'),
    () => console.log('Completed')
);

Si l'appel HTTP réussit, on voit les logs suivant :

Next
Completed

En cas de succès, la valeur est émise dans la fonction NEXT. Puis l'observable se ferme et il passe dans la fonction COMPLETE. C'est la fin du lifecycle de l'observable, aucune erreur n'a été émise.

Si l'appel HTTP échoue, on voit les logs suivant :

Error

En cas d'erreur, aucune valeur n'est émise dans la fonction NEXT. On passe dans la fonction ERROR, c'est la fin du lifecycle de l'observable.

A savoir :

  • Un appel HTTP est un observable qui "complete" après avoir émit une valeur. On a alors deux "chemins" possibles :

image

  • On ne peut pas être dans un COMPLETE et ERROR dans le lifecycle d'un observable, c'est soit l'un, soit l'autre.

Pour résoudre le problème

Pour gérer l'affichage du loader en cas d'erreur, on va traiter son état dans la fonction NEXT et dans la fonction ERROR.

export class HobbitsComponent implements OnInit {

  isLoading = true;
  hobbits: Hobbit[] = [];

  constructor(private hobbitsService: HobbitsService) {}

  ngOnInit() {
    this.hobbitsService.findHobbits().subscribe(
      (hobbits: Hobbit[]) => {
        this.hobbits = hobbits;
        this.isLoading = false;
      },
      () => {
        this.isLoading = false;
      }
    );
  }
}

Si l'appel HTTP réussit ou échoue, on aura le booléen isLoading à false et donc on n'aura plus le loader affiché à l'infini.

Traiter ou logger l'erreur

Dans le cas où on veut utiliser l'erreur pour debugger ou pour afficher un message précis à l'utilisateur par exemple, on peut utiliser l'erreur retournée comme ceci :

this.hobbitsService.findHobbits().subscribe(
    () => console.log('Next'),
    (error) => console.log('Error', error),
    () => console.log('Completed')
);

Gestion les erreurs dans un effect

Pour gérer vos effets de bord, par exemple vos appels backends, vous pouvez également utiliser la librarie NGRX et les effects. Personnellement c'est la manière dont je gère ces appels. Je ne donne pas la responsabilité au composant de récupérer les données.

L'action loadHobbits met un booléen isLoading à true dans le store. L'action loadHobbitsSuccess passe ce booléen à false et enregistre la liste des Hobbits dans le store. Le loader est affiché si le booléen isLoading est à true

Exemple sans gestion d'erreur

@Injectable()
export class HobbitsEffects {

  loadHobbits$ = createEffect(() =>
    this.actions$.pipe(
      ofType(loadHobbits),
      concatMap(() =>
        this.hobbitsService.findHobbits().pipe(
          map((hobbits: Hobbit[]) => loadHobbitsSuccess({ hobbits }))
        )
      )
    )
  );

  constructor(
      private actions$: Actions,
      private hobbitsService: HobbitsService
  ) {}
}

Que se passe-t-il si l'appel findHobbits échoue ?

Le loader va être affiché, sans s'arrêter, alors que l'appel est terminé.

Pourquoi ?

Seul l'action loadHobbitsSuccess met le booléen isLoading à false. Or, en cas d'erreur, on ne passe pas dans le map qui suit l'appel HTTP. Il faudrait attraper l'erreur à l'aide de l'opérateur catchError.

catchError

L'opérateur catchError va permettre, comme son nom l'indique, d'attraper l'erreur et de retourner un nouvel observable.

this.hobbitsService.findHobbits().pipe(
    map(() => /*SUCCESS*/),
    catchError(() => of(/*ERROR*/)),
);

Pour résoudre le problème

On va créer une nouvelle action loadHobbitsError qui va permettre dans notre exemple de mettre le booléen isLoading à false et donc d'arrêter d'afficher le loader en cas d'erreur.

@Injectable()
export class HobbitsEffects {

  loadHobbits$ = createEffect(() =>
    this.actions$.pipe(
      ofType(loadHobbits),
      concatMap(() =>
        this.hobbitsService.findHobbits().pipe(
          map((hobbits: Hobbit[]) => loadHobbitsSuccess({ hobbits })),
          catchError(() => of(loadHobbitsError()))
        )
      )
    )
  );

  constructor(
      private actions$: Actions,
      private hobbitsService: HobbitsService
  ) {}
}

A savoir :

  • Si vous êtes sur une version antérieure à la version 8 d'NGRX, en cas d'erreur "non attrapée" dans l'observable principal à l'aide d'un catchError, l'effect est complete. Depuis la version 8, si aucune erreur est "attrapée" dans l'observable principal, l'effect se resouscrit avec une limite maximum d'erreurs.

Appels multiples

En cas d'appels multiples, on peut choisir de retourner un observable avec des données pour gérer les cas d'appels qui ont échoués.

Dans l'exemple ci-dessous, on a une liste d'ids d'Hobbits donnée par l'action loadHobbitsBeers.
Pour chaque id d'Hobbit, on fait un appel HTTP via favoriteBeersByHobbitId qui va retourner une liste de string qui correspond aux bières préférées d'un Hobbit donné.
Ces appels sont effectués en parallèles, et si l'un d'eux échoue, on enregistre l'id du Hobbit, ainsi que la bière Prancing Pony's Ale par défaut. Ainsi, les appels qui ont échoué sont traités avec des données par défaut.

@Injectable()
export class HobbitsEffects {
  loadHobbitsDetails$ = createEffect(() =>
    this.actions$.pipe(
      ofType(loadHobbitsBeers),
      mergeMap(({ hobbitsIds }) =>
        forkJoin(
          hobbitsIds.map(hobbitId =>
            this.hobbitsService.favoriteBeersByHobbitId(hobbitId).pipe(
              map((beers: string[]) => ({
                id: hobbitId,
                beers,
              })),
              catchError(() =>
                of({
                  id: hobbitId,
                  beers: [`Prancing Pony's Ale`]
                })
              )
            )
          )
        )
      ),
      map((hobbitsBeers: HobbitsBeers[]) => loadHobbitsBeersSuccess({ hobbitsBeers }))
    )
  );

  constructor(
      private actions$: Actions,
      private hobbitsService: HobbitsService
  ) {}
}

Traiter ou logger l'erreur

Dans le cas où on veut utiliser l'erreur pour debugger ou pour afficher un message précis à l'utilisateur par exemple, on peut utiliser l'erreur retournée comme ceci :

this.hobbitsService.findHobbits().pipe(
  map((hobbits: Hobbit[]) => /*SUCCESS*/),
  catchError((error) => { 
      console.log('ERROR', error);
      return of(/*ERROR*/);
  })
)


This content originally appeared on DEV Community and was authored by Clara Belair


Print Share Comment Cite Upload Translate Updates
APA

Clara Belair | Sciencx (2021-04-09T12:28:42+00:00) Gestion des erreurs RXJS -NGRX. Retrieved from https://www.scien.cx/2021/04/09/gestion-des-erreurs-rxjs-ngrx/

MLA
" » Gestion des erreurs RXJS -NGRX." Clara Belair | Sciencx - Friday April 9, 2021, https://www.scien.cx/2021/04/09/gestion-des-erreurs-rxjs-ngrx/
HARVARD
Clara Belair | Sciencx Friday April 9, 2021 » Gestion des erreurs RXJS -NGRX., viewed ,<https://www.scien.cx/2021/04/09/gestion-des-erreurs-rxjs-ngrx/>
VANCOUVER
Clara Belair | Sciencx - » Gestion des erreurs RXJS -NGRX. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/04/09/gestion-des-erreurs-rxjs-ngrx/
CHICAGO
" » Gestion des erreurs RXJS -NGRX." Clara Belair | Sciencx - Accessed . https://www.scien.cx/2021/04/09/gestion-des-erreurs-rxjs-ngrx/
IEEE
" » Gestion des erreurs RXJS -NGRX." Clara Belair | Sciencx [Online]. Available: https://www.scien.cx/2021/04/09/gestion-des-erreurs-rxjs-ngrx/. [Accessed: ]
rf:citation
» Gestion des erreurs RXJS -NGRX | Clara Belair | Sciencx | https://www.scien.cx/2021/04/09/gestion-des-erreurs-rxjs-ngrx/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.