Перейти к содержимому

10. Обработка ошибок

Привет! Яша снова здесь. Сегодня разбираем одну из самых важных тем — что делать, когда что-то пошло не так. В RxJS ошибки — это первоклассные граждане потока, и у нас есть целый арсенал инструментов для работы с ними.

Когда в Observable возникает ошибка, поток завершается. Ошибка «падает вниз» по цепочке операторов до первого обработчика.

import { throwError, of } from 'rxjs';
import { map } from 'rxjs/operators';
// Ошибка в операторе завершает поток
of(1, 2, 3).pipe(
map(n => {
if (n === 2) throw new Error('Число 2 — плохое!');
return n;
})
).subscribe({
next: n => console.log('next:', n), // 1
error: e => console.log('error:', e.message), // Число 2 — плохое!
complete: () => console.log('done'), // НЕ вызовется
});

💡 Важно: ошибка, брошенная внутри оператора, обрабатывается цепочкой. Ошибка, брошенная вне — например, синхронно в subscribe — падает в глобальный обработчик и НЕ перехватывается catchError.


throwError создаёт Observable, который немедленно бросает ошибку и завершается.

import { throwError } from 'rxjs';
// Фабрика ошибки (рекомендуемый способ в RxJS 7+)
const error$ = throwError(() => new Error('Что-то сломалось'));
error$.subscribe({
next: v => console.log(v), // не вызовется
error: e => console.log(e.message), // Что-то сломалось
});

EMPTY — Observable, который сразу завершается без значений и без ошибок. Удобен как «заглушка» при обработке ошибок.

import { EMPTY, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
// Если id нет — не делаем ничего
const fetchUser = (id: string | null) =>
id ? of({ name: 'Яша', id }) : EMPTY;
fetchUser(null).subscribe({
next: u => console.log(u),
complete: () => console.log('завершено без значений'),
});

catchError перехватывает ошибку и позволяет вернуть другой Observable — продолжить поток или завершить его.

import { of, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
const api$ = throwError(() => new Error('500 Internal Server Error'));
api$.pipe(
catchError(err => {
console.log('Поймали ошибку:', err.message);
return of({ data: null, error: err.message }); // возвращаем fallback
})
).subscribe(console.log);
// { data: null, error: '500 Internal Server Error' }

catchError получает два аргумента: (error, caught$). Второй — исходный Observable, что позволяет переподписаться:

import { of, throwError, timer } from 'rxjs';
import { catchError, switchMap } from 'rxjs/operators';
// Переподписка с задержкой — ручной retry
let attempt = 0;
const flaky$ = new Observable(obs => {
attempt++;
if (attempt < 3) obs.error(new Error(`Попытка ${attempt} неудачна`));
else { obs.next('Успех!'); obs.complete(); }
});
flaky$.pipe(
catchError((err, caught$) => {
console.log(err.message);
return timer(1000).pipe(switchMap(() => caught$));
})
).subscribe(console.log); // Через 2 секунды: Успех!

retry(n) автоматически переподписывается на источник при ошибке, до n раз.

import { throwError, of } from 'rxjs';
import { retry, map } from 'rxjs/operators';
let calls = 0;
const flakyApi$ = new Observable<string>(obs => {
calls++;
console.log(`Запрос #${calls}`);
if (calls < 3) obs.error(new Error('Сеть упала'));
else { obs.next('Данные получены ✅'); obs.complete(); }
});
flakyApi$.pipe(
retry(3) // попробуем ещё 3 раза при ошибке
).subscribe({
next: v => console.log(v),
error: e => console.log('Всё, сдаёмся:', e.message),
});
// Запрос #1, Запрос #2, Запрос #3 → Данные получены ✅

В RxJS 7+ retry принимает конфиг-объект:

import { retry } from 'rxjs/operators';
flakyApi$.pipe(
retry({
count: 3,
delay: 1000, // ждать 1 секунду между попытками
resetOnSuccess: true, // сбросить счётчик после успеха
})
).subscribe(console.log);

retryWhen даёт полный контроль: принимает Observable ошибок и позволяет задать любую логику задержки.

import { retryWhen, delay, take, tap } from 'rxjs/operators';
import { throwError, timer } from 'rxjs';
flakyApi$.pipe(
retryWhen(errors$ =>
errors$.pipe(
tap((err, i) => console.log(`Ошибка ${i + 1}: ${err.message}`)),
delay(2000), // экспоненциальная задержка вручную
take(3), // максимум 3 повтора
)
)
).subscribe(console.log);

⚠️ retryWhen устарел в RxJS 7. Используйте retry({ delay: ... }) или retry({ delay: (err, count) => timer(count * 1000) }).


onErrorResumeNextWith — игнорировать ошибки и продолжать

Заголовок раздела «onErrorResumeNextWith — игнорировать ошибки и продолжать»

onErrorResumeNextWith (ранее onErrorResumeNext) переключается на следующий Observable при ошибке без передачи ошибки подписчику.

import { onErrorResumeNextWith } from 'rxjs/operators';
import { throwError, of } from 'rxjs';
throwError(() => new Error('Провал!')).pipe(
onErrorResumeNextWith(of('резервный источник'), of('и ещё один'))
).subscribe(console.log);
// резервный источник
// и ещё один

ОператорЧто делает при ошибкеПередаёт ошибку?
catchErrorПереключается на другой ObservableНет (если вернуть Observable)
retry(n)Переподписывается N разДа (если все попытки исчерпаны)
retryWhenПереподписывается по условиюДа (если источник завершился)
onErrorResumeNextWithПереходит к следующему ObservableНет
EMPTYТихое завершениеНет

Попробуйте примеры в интерактивном редакторе: