RxJS-Fehlerbehandlungsstrategien
Die Fehlerbehandlung in RxJS ist ein wichtiger Aspekt der reaktiven Programmierung. Durch Implementierung angemessener Fehlerbehandlung werden die Robustheit und Zuverlässigkeit Ihrer Anwendung verbessert. Dieses Dokument erklärt verschiedene Fehlerbehandlungsstrategien, die in RxJS verwendet werden können.
Grundmuster
In RxJS behandeln Sie Fehler als Teil des Observable-Lebenszyklus. Die grundlegende Fehlerbehandlung umfasst folgende Methoden:
import { of, throwError } from 'rxjs';
import { catchError } from 'rxjs';
// Observable, das einen Fehler generiert
const error$ = throwError(() => new Error('Fehler aufgetreten')); // Ab RxJS 7, Funktionsform empfohlen
// Grundlegende Fehlerbehandlung
error$
.pipe(
catchError((error) => {
console.error('Fehler abgefangen:', error.message);
return of('Fallback-Wert nach Fehler');
})
)
.subscribe({
next: (value) => console.log('Wert:', value),
error: (err) => console.error('Nicht behandelter Fehler:', err),
complete: () => console.log('Abgeschlossen'),
});
// Ausgabe:
// Fehler abgefangen: Fehler aufgetreten
// Wert: Fallback-Wert nach Fehler
// AbgeschlossenVerschiedene Fehlerbehandlungsstrategien
1. Fehler abfangen und alternativen Wert bereitstellen
Verwenden Sie den catchError-Operator, um Fehler abzufangen und einen alternativen Wert oder Stream bereitzustellen.
import { of, throwError } from 'rxjs';
import { catchError } from 'rxjs';
const source$ = throwError(() => new Error('Datenabruffehler'));
source$.pipe(
catchError(error => {
console.error('Fehler aufgetreten:', error.message);
// Alternative Daten zurückgeben
return of({ isError: true, data: [], message: 'Standarddaten werden angezeigt' });
})
).subscribe(data => console.log('Ergebnis:', data));
// Ausgabe:
// Fehler aufgetreten: Datenabruffehler
// Ergebnis: {isError: true, data: Array(0), message: 'Standarddaten werden angezeigt'}2. Bei Fehler wiederholen
Verwenden Sie die Operatoren retry oder retryWhen, um den Stream bei Fehlerauftreten zu wiederholen.
import { interval, throwError, of } from 'rxjs';
import { mergeMap, retry, tap } from 'rxjs';
let attemptCount = 0;
interval(1000).pipe(
mergeMap(val => {
if (++attemptCount <= 2) {
return throwError(() => new Error(`Fehler #${attemptCount}`));
}
return of('Erfolg!');
}),
tap(() => console.log('Ausführung:', attemptCount)),
retry(2), // Bis zu 2x wiederholen
).subscribe({
next: value => console.log('Wert:', value),
error: err => console.error('Finaler Fehler:', err.message),
});
// Ausgabe:
// Ausführung: 3
// Wert: Erfolg!
// Ausführung: 4
// Wert: Erfolg!
// Ausführung: 5
// ...3. Wiederholung mit exponentiellem Backoff
Bei Netzwerkanfragen usw. ist „exponentielles Backoff", das die Wiederholungsintervalle schrittweise verlängert, effektiv.
import { throwError, timer, of } from 'rxjs';
import { retryWhen, tap, concatMap, catchError } from 'rxjs';
function fetchWithRetry() {
let retryCount = 0;
return throwError(() => new Error('Netzwerkfehler')).pipe(
retryWhen((errors) =>
errors.pipe(
// Fehlerzählung
tap((error) => console.log('Fehler aufgetreten:', error.message)),
// Verzögerung mit exponentiellem Backoff
concatMap(() => {
retryCount++;
const delayMs = Math.min(1000 * Math.pow(2, retryCount), 10000);
console.log(`${retryCount}. Wiederholung in ${delayMs}ms`);
return timer(delayMs);
}),
// Bis zu 5 Wiederholungen
tap(() => {
if (retryCount >= 5) {
throw new Error('Maximale Wiederholungsanzahl überschritten');
}
})
)
),
// Finaler Fallback
catchError((error) => {
console.error('Alle Wiederholungen fehlgeschlagen:', error.message);
return of({
error: true,
message: 'Verbindung fehlgeschlagen. Bitte später erneut versuchen.',
});
})
);
}
fetchWithRetry().subscribe({
next: (result) => console.log('Ergebnis:', result),
error: (err) => console.error('Nicht behandelter Fehler:', err),
});
// Ausgabe:
// Fehler aufgetreten: Netzwerkfehler
// 1. Wiederholung in 2000ms
// Fehler aufgetreten: Netzwerkfehler
// 2. Wiederholung in 4000ms
// Fehler aufgetreten: Netzwerkfehler
// 3. Wiederholung in 8000ms
// Fehler aufgetreten: Netzwerkfehler
// 4. Wiederholung in 10000ms
// Fehler aufgetreten: Netzwerkfehler
// 5. Wiederholung in 10000ms
// Alle Wiederholungen fehlgeschlagen: Maximale Wiederholungsanzahl überschritten
// Ergebnis: {error: true, message: 'Verbindung fehlgeschlagen. Bitte später erneut versuchen.'}4. Ressourcenfreigabe bei Fehlerauftreten
Verwenden Sie den finalize-Operator, um Ressourcen freizugeben, wenn der Stream durch Abschluss oder Fehler beendet wird. finalize ist nicht nur bei Fehlerauftreten wirksam, sondern auch bei normalem Abschluss, wenn Sie zuverlässig Cleanup-Verarbeitung durchführen möchten.
import { throwError } from 'rxjs';
import { catchError, finalize } from 'rxjs';
let isLoading = true;
throwError(() => new Error('Verarbeitungsfehler'))
.pipe(
catchError((error) => {
console.error('Fehlerverarbeitung:', error.message);
return throwError(() => error); // Fehler erneut werfen
}),
finalize(() => {
isLoading = false;
console.log('Ladestatus zurücksetzen:', isLoading);
})
)
.subscribe({
next: (value) => console.log('Wert:', value),
error: (err) => console.error('Finaler Fehler:', err.message),
complete: () => console.log('Abgeschlossen'),
});
// Ausgabe:
// Fehlerverarbeitung: Verarbeitungsfehler
// Finaler Fehler: Verarbeitungsfehler
// Ladestatus zurücksetzen: falseFehlerbehandlungsmuster
Fehlerbehandlung einschließlich UI-Element-Anzeigesteuerung
import { of, throwError } from 'rxjs';
import { catchError, finalize, tap } from 'rxjs';
function fetchData(shouldFail = false) {
// Ladeanzeige
showLoadingIndicator();
// Datenabruf (Erfolg oder Fehler)
return (
shouldFail
? throwError(() => new Error('API-Fehler'))
: of({ name: 'Daten', value: 42 })
).pipe(
tap((data) => {
// Verarbeitung bei Erfolg
updateUI(data);
}),
catchError((error) => {
// UI-Aktualisierung bei Fehler
showErrorMessage(error.message);
// Leere Daten oder Standardwert zurückgeben
return of({ name: 'Standard', value: 0 });
}),
finalize(() => {
// Ladeanzeige ausblenden, unabhängig von Erfolg oder Fehler
hideLoadingIndicator();
})
);
}
// Hilfsfunktionen für UI-Operationen
function showLoadingIndicator() {
console.log('Ladeanzeige');
}
function hideLoadingIndicator() {
console.log('Ladeanzeige ausgeblendet');
}
function updateUI(data: { name: string; value: number }) {
console.log('UI aktualisiert:', data);
}
function showErrorMessage(message: any) {
console.log('Fehler anzeigen:', message);
}
// Verwendungsbeispiel
fetchData(true).subscribe();
// Ausgabe:
// Ladeanzeige
// Fehler anzeigen: API-Fehler
// Ladeanzeige ausgeblendetVerarbeitung mehrerer Fehlerquellen
import { forkJoin, of, throwError } from 'rxjs';
import { catchError, map } from 'rxjs';
// Mehrere API-Anfragen simulieren
function getUser() {
return of({ id: 1, name: 'Taro Yamada' });
}
function getPosts() {
return throwError(() => new Error('Beitragsabruffehler'));
}
function getComments() {
return throwError(() => new Error('Kommentarabruffehler'));
}
// Alle Daten abrufen und teilweise Fehler zulassen
forkJoin({
user: getUser().pipe(
catchError((error) => {
console.error('Benutzerabruffehler:', error.message);
return of(null); // Bei Fehler null zurückgeben
})
),
posts: getPosts().pipe(
catchError((error) => {
console.error('Beitragsabruffehler:', error.message);
return of([]); // Bei Fehler leeres Array zurückgeben
})
),
comments: getComments().pipe(
catchError((error) => {
console.error('Kommentarabruffehler:', error.message);
return of([]); // Bei Fehler leeres Array zurückgeben
})
),
})
.pipe(
map((result) => ({
...result,
// Flag hinzufügen, das anzeigt, ob teilweise Fehler aufgetreten sind
hasErrors:
!result.user ||
result.posts.length === 0 ||
result.comments.length === 0,
}))
)
.subscribe((data) => {
console.log('Endergebnis:', data);
if (data.hasErrors) {
console.log(
'Teilweiser Datenabruf fehlgeschlagen, aber verfügbare Daten werden angezeigt'
);
}
});
// Ausgabe:
// Beitragsabruffehler: Beitragsabruffehler
// Kommentarabruffehler: Kommentarabruffehler
// Endergebnis: {user: {…}, posts: Array(0), comments: Array(0), hasErrors: true}
// Teilweiser Datenabruf fehlgeschlagen, aber verfügbare Daten werden angezeigtBest Practices für Fehlerbehandlung
Fehler immer abfangen: Fügen Sie in Observable-Ketten immer Fehlerbehandlung hinzu. Besonders wichtig bei lang laufenden Streams.
Aussagekräftige Fehlermeldungen bereitstellen: Fehlerobjekte sollten Informationen enthalten, die helfen, Ort und Ursache zu identifizieren.
Ressourcen angemessen freigeben: Verwenden Sie
finalize, um sicherzustellen, dass Ressourcen unabhängig von Erfolg oder Fehler freigegeben werden.Wiederholungsstrategie berücksichtigen: Besonders bei Netzwerkoperationen verbessert die Implementierung einer angemessenen Wiederholungsstrategie die Zuverlässigkeit.
Benutzerfreundliche Fehlerbehandlung: Zeigen Sie in der UI keine technischen Fehlermeldungen direkt an, sondern stellen Sie Informationen bereit, die Benutzer verstehen können.
// Beispiel: Umwandlung in benutzerfreundliche Fehlermeldungen
function getErrorMessage(error: any): string {
if (error.status === 401) {
return 'Sitzung abgelaufen. Bitte erneut anmelden.';
} else if (error.status === 404) {
return 'Angeforderte Ressource nicht gefunden.';
} else if (error.status >= 500) {
return 'Serverfehler aufgetreten. Bitte später erneut versuchen.';
}
return 'Unerwarteter Fehler aufgetreten.';
}Zusammenfassung
Die Fehlerbehandlung in RxJS ist ein wichtiger Teil zur Gewährleistung der Robustheit Ihrer Anwendung. Durch angemessene Kombination von Operatoren wie catchError, retry und finalize können Sie verschiedene Fehlerszenarien bewältigen. Entwerfen Sie eine umfassende Fehlerbehandlungsstrategie, um nicht nur Fehler abzufangen, sondern auch die Benutzererfahrung zu verbessern.
🔗 Verwandte Abschnitte
- Häufige Fehler und Lösungen - Anti-Muster in der Fehlerbehandlung überprüfen
- retry und catchError - Detailliertere Anwendungsmethoden erklärt