Kurs Angular · Moduł 9: HttpClient
Trzy tarcze przed awarią - błędy i cache
W tej lekcji3
Serwer bywa jak pogoda na polu bitwy: raz zawiedzie połączenie, raz odpowie za wolno. Samuraj nie pada po pierwszym potknięciu - ma tarcze. W tej lekcji poznasz trzy: retry (spróbuj jeszcze raz), catchError (zamień surowy błąd na zrozumiały komunikat) i cache (nie pytaj dwa razy o to samo). Idą po kolei, od reakcji na błąd po unikanie zbędnej pracy.
Tarcza pierwsza: spróbuj ponownie, potem tłumacz błąd
Wiele błędów jest chwilowych - sieć mrugnęła i za sekundę działa. Zamiast od razu poddawać się, ponów żądanie kilka razy, a dopiero uporczywy błąd zamień na czytelny komunikat.
getSamurai(id: number): Observable<Samurai> {
return this.http.get<Samurai>('/api/samurai/' + id).pipe(
retry({ count: 3, delay: 1000 }), // 3 proby co sekunde
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse): Observable<never> {
let message = 'Nieznany blad!';
if (error.status === 0) message = 'Brak polaczenia z serwerem';
else if (error.status === 404) message = 'Samurai nie zostal znaleziony';
else if (error.status >= 500) message = 'Blad serwera - sprobuj pozniej';
else if (error.error?.message) message = error.error.message;
return throwError(() => new Error(message));
}Prześledź dwie tarcze naraz. retry({ count: 3, delay: 1000 }) powtarza żądanie do trzech razy w odstępach sekundy - jeśli błąd był chwilowy, użytkownik niczego nie zauważy. Gdy mimo to się nie uda, catchError woła handleError, który po statusie rozpoznaje przyczynę i zwraca ludzki komunikat zamiast surowego HttpErrorResponse. Zwróć uwagę na status 0 - to nie odpowiedź serwera, lecz brak połączenia; warto go obsłużyć osobno, bo znaczy coś zupełnie innego niż 500.
Tarcza druga: cache, czyli nie pytaj dwa razy
Jeśli dziesięć komponentów prosi o tego samego samuraja, po co dziesięć żądań? Cache zapamiętuje raz pobrany strumień i oddaje go kolejnym proszącym.
export class CachedSamuraiService {
private cache = new Map<string, Observable<Samurai>>();
getSamurai(id: number): Observable<Samurai> {
const key = 'samurai-' + id;
if (!this.cache.has(key)) {
this.cache.set(key,
this.http.get<Samurai>('/api/samurai/' + id).pipe(
shareReplay({ bufferSize: 1, refCount: true })
)
);
}
return this.cache.get(key)!;
}
}Sercem jest shareReplay({ bufferSize: 1 }) - to on sprawia, że żądanie leci raz, a jego wynik jest odtwarzany każdemu, kto później zapyta. Mapa cache trzyma gotowe strumienie pod kluczem, więc drugie getSamurai(5) nie dotknie serwera, tylko odda zapamiętany strumień. Bez shareReplay każdy subskrybent wywołałby nowe żądanie - to najczęstsza pułapka przy ręcznym cache'owaniu.
Tarcza trzecia: cache, który się starzeje
Zapamiętane dane w końcu się dezaktualizują. Dokładasz więc czas wygaśnięcia (TTL): po nim cache sam sięgnie po świeże dane.
export class TimedCacheService {
private cache = new Map<string, { data$: Observable<any>; expiry: number }>();
private readonly TTL = 5 * 60 * 1000; // 5 minut
get<T>(url: string): Observable<T> {
const cached = this.cache.get(url);
if (cached && cached.expiry > Date.now()) {
return cached.data$; // wciaz swieze - oddaj z cache
}
const data$ = this.http.get<T>(url).pipe(shareReplay({ bufferSize: 1 }));
this.cache.set(url, { data$, expiry: Date.now() + this.TTL });
return data$;
}
}Różnica względem prostego cache to jedna linia porównania: cached.expiry > Date.now(). Jeśli wpis jest młodszy niż pięć minut, oddajesz go; jeśli starszy, pobierasz na nowo i zapisujesz ze świeżą datą wygaśnięcia. To rozsądny kompromis: oszczędzasz serwer, ale nie pokazujesz danych sprzed godziny. Dobór TTL zależy od danych - lista klanów może żyć godzinę, stan bitwy tylko sekundy.
Pamiętaj z tej lekcji trzy tarcze: retry ratuje przed chwilową awarią, catchError tłumaczy błąd na komunikat, a cache (z shareReplay, opcjonalnie z TTL) chroni serwer przed powtarzaniem tej samej pracy. Razem czynią aplikację odporną jak zbroja.
Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Którego operatora RxJS używamy do obsługi błędów HTTP?
2. Jaki status ma HttpErrorResponse, gdy w ogóle nie udało się połączyć z serwerem (błąd sieciowy)?
To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.
Zadania praktyczne w grze
- Edytor kodu
Uzupełnij obsługę błędów w serwisie z logowaniem i re-throw.
- Układanie w pionie
Uporządkuj kolejne linie strumienia HTTP z ponawianiem i przechwytywaniem błędu:
- Klikanie w kolejności
Zbuduj wywołanie re-emitujące błąd ze strumienia: