Gestion d'erreurs RxJS : catchError, retry, throwError, errorHandler, logging et user feedback. Stratégies robustes et bonnes pratiques.
Introduction
Gérer les erreurs correctement est crucial pour une application robuste en production. RxJS offre des outils puissants pour capturer, transformer, récupérer et logger les erreurs sans bloquer le flux réactif.
catchError() — Intercepter et transformer
catchError() intercepte les erreurs et vous permet de les gérer ou les transformer. C'est votre filet de sécurité :
// Exemple basique
import { catchError } from 'rxjs/operators';
import { throwError, of } from 'rxjs';
this.userService.getUsers().pipe(
catchError(error => {
// Option 1 : Logger et relancer l'erreur
console.error('Erreur lors du chargement :', error);
return throwError(() => new Error('Impossible de charger les utilisateurs'));
// Option 2 : Logger et retourner une valeur par défaut
// return of([]); // Retourne un tableau vide
// Option 3 : Logger et afficher un message à l'utilisateur
// this.showNotification('Erreur: ' + error.message);
// return of([]);
})
).subscribe(users => {
this.users = users; // users = [] si erreur
});
- ✅
throwError()propage l'erreur vers le subscribe - ✅
of(defaultValue)récupère l'erreur et continue - ✅
EMPTYtermine silencieusement l'Observable - ❌ Sans catchError, une erreur tue l'Observable
retry() — Réessayer automatiquement
retry() réessaye automatiquement si l'Observable échoue. Utile pour les erreurs réseau temporaires :
import { retry, catchError } from 'rxjs/operators';
this.http.get('/api/users').pipe(
// Réessaye 3 fois avant d'échouer
retry(3),
catchError(error => {
console.error('Échec après 3 tentatives');
return of([]);
})
).subscribe(users => {
this.users = users;
});
// Avec délai entre les tentatives (Angular 16+)
this.http.get('/api/users').pipe(
retry({
count: 3, // Nombre de tentatives
delay: 1000, // Délai en ms entre les tentatives
// delay: (error, retryCount) => { // Délai dynamique (exponential backoff)
// return timer(Math.pow(2, retryCount) * 1000);
// }
}),
catchError(error => {
this.logger.error('Échec après 3 tentatives', error);
return of([]);
})
).subscribe();
- ✅ Erreurs réseau temporaires (timeout, connexion perdue)
- ✅ Services instables avec pics de charge
- ❌ Erreurs métier (401 Unauthorized, 400 Bad Request) — ne réessayez pas, c'est inutile
Patterns robustes pour la production
Voici une stratégie complète combinant retry, timeout, logging et fallback :
// error.interceptor.ts — Gestion centralisée des erreurs HTTP
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpErrorResponse } from '@angular/common/http';
import { catchError, retry, timeout } from 'rxjs/operators';
import { throwError, timer } from 'rxjs';
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
constructor(private logger: LoggerService, private notify: NotificationService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
return next.handle(req).pipe(
// Timeout après 10 secondes
timeout(10000),
// Réessaye 3 fois avec délai exponentiel
retry({
count: 3,
delay: (error, retryCount) => {
// 1s, 2s, 4s
return timer(Math.pow(2, retryCount) * 1000);
}
}),
// Capturer et gérer les erreurs
catchError((error: HttpErrorResponse) => {
let message = 'Une erreur est survenue';
if (error.status === 0) {
message = 'Problème de connexion réseau';
} else if (error.status === 401) {
message = 'Session expirée. Reconnexion requise';
// Rediriger vers login...
} else if (error.status === 403) {
message = 'Accès refusé';
} else if (error.status === 404) {
message = 'Ressource non trouvée';
} else if (error.status === 500) {
message = 'Erreur serveur. Réessai en cours...';
}
// Logger pour les développeurs
this.logger.error(`HTTP ${error.status}`, error);
// Notifier l'utilisateur
this.notify.error(message);
return throwError(() => error);
})
);
}
}
Logging et debugging
Utilisez tap() pour logger sans affecter le flux. C'est utile pour déboguer :
import { tap } from 'rxjs/operators';
// Tracer le flux complet
this.http.get('/api/users').pipe(
tap({
next: (users) => console.log('✓ Utilisateurs reçus :', users.length),
error: (error) => console.error('✗ Erreur :', error),
complete: () => console.log('✓ Requête terminée')
}),
retry(1),
catchError(error => {
return of([]);
})
).subscribe(users => {
this.users = users;
});
// RxJS Debugger Chrome — Visualiser le flux en temps réel
// npm install rxjs-devtools
import { debug } from 'rxjs-devtools/operators';
this.http.get('/api/users').pipe(
debug('USERS'), // Affiche dans la console RxJS Debugger
catchError(error => of([]))
).subscribe();
Conclusion
Une bonne gestion d'erreurs rend votre application production-ready. Combinaison gagnante :
timeout()pour éviter les requêtes qui traînentretry()pour les erreurs réseau temporairescatchError()pour un fallback utilisateurtap()pour logger sans pollution- HttpInterceptor centralisé pour une logique partagée
catchError(). Pas d'exception à cette règle.