В Angular работа с HTTP строится поверх HttpClient,
который возвращает поток Observable. Любая ошибка
HTTP-запроса в этой архитектуре считается ошибкой
потока, а не обычным значением. Это означает, что ошибка
прерывает поток данных и должна обрабатываться специальными механизмами
RxJS.
HTTP-ошибки условно делятся на два типа:
Angular объединяет оба типа в объект
HttpErrorResponse.
При возникновении ошибки HttpClient выбрасывает
экземпляр HttpErrorResponse. Его ключевые свойства:
ProgressEvent).Пример структуры:
{
status: 404,
statusText: "Not Found",
message: "Http failure response for /api/users: 404 Not Found",
error: { detail: "User not found" }
}
Самый базовый способ — использование второго аргумента метода
subscribe.
this.http.get<User[]>('/api/users').subscribe(
data => {
this.users = data;
},
error => {
console.error(error);
}
);
Недостатки подхода:
Для более гибкой обработки ошибок применяется оператор
catchError.
import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';
this.http.get<User[]>('/api/users').pipe(
catchError(error => {
return throwError(() => error);
})
);
catchError позволяет:
Часто сервер возвращает технические данные, непригодные для отображения. Логика преобразования может выглядеть следующим образом:
catchError((error: HttpErrorResponse) => {
let message = 'Неизвестная ошибка';
if (error.error instanceof ErrorEvent) {
message = 'Ошибка сети или клиента';
} else {
switch (error.status) {
case 400:
message = 'Некорректный запрос';
break;
case 401:
message = 'Требуется авторизация';
break;
case 403:
message = 'Доступ запрещён';
break;
case 500:
message = 'Ошибка сервера';
break;
}
}
return throwError(() => message);
});
Создание отдельного сервиса позволяет избежать повторения кода.
@Injectable({ providedIn: 'root' })
export class HttpErrorService {
handle(error: HttpErrorResponse): Observable<never> {
let message = 'Ошибка запроса';
if (error.status === 0) {
message = 'Проблема с соединением';
}
return throwError(() => message);
}
}
Использование:
this.http.get('/api/data').pipe(
catchError(error => this.errorService.handle(error))
);
Интерсепторы позволяют перехватывать все HTTP-запросы и ответы приложения.
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
intercept(
req: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
// глобальная логика
return throwError(() => error);
})
);
}
}
Регистрация:
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: ErrorInterceptor,
multi: true
}
]
Преимущества:
Типичный сценарий — истёкший токен (401 Unauthorized).
if (error.status === 401) {
this.authService.logout();
this.router.navigate(['/login']);
}
Такой код обычно размещается внутри интерсептора, чтобы реагировать на ошибки независимо от конкретного запроса.
RxJS предоставляет оператор retry и
retryWhen.
this.http.get('/api/data').pipe(
retry(3)
);
Для более сложной логики:
retryWhen(errors =>
errors.pipe(
delay(1000),
take(3)
)
);
Подход применяется для нестабильных сетевых условий или временных ошибок сервера.
При использовании async pipe ошибка Observable не
отображается напрямую. Поток просто завершается.
<div *ngIf="data$ | async as data">
{{ data }}
</div>
Для отображения ошибок используется обёртка потока:
data$ = this.http.get('/api/data').pipe(
catchError(err => of({ error: true }))
);
Интерсептор часто используется для отправки ошибок во внешние системы:
catchError(error => {
this.logger.log(error);
return throwError(() => error);
});
Логирование должно быть отделено от отображения ошибок пользователю.
Сервер может возвращать корректный HTTP-статус, но бизнес-ошибку в теле ответа.
{
"success": false,
"errorCode": "USER_BLOCKED"
}
В этом случае ошибка не считается HTTP-ошибкой и обрабатывается в
map, а не catchError.
map(response => {
if (!response.success) {
throw new Error(response.errorCode);
}
return response;
});
Типовая архитектура включает:
Такой подход обеспечивает предсказуемое поведение приложения, упрощает сопровождение и снижает связанность кода.