Отлов ошибок в Angular HttpClient

У меня есть служба данных, которая выглядит так:

@Injectable()
export class DataService {
    baseUrl = 'http://localhost'
        constructor(
        private httpClient: HttpClient) {
    }
    get(url, params): Promise<Object> {

        return this.sendRequest(this.baseUrl + url, 'get', null, params)
            .map((res) => {
                return res as Object
            })
            .toPromise();
    }
    post(url, body): Promise<Object> {
        return this.sendRequest(this.baseUrl + url, 'post', body)
            .map((res) => {
                return res as Object
            })
            .toPromise();
    }
    patch(url, body): Promise<Object> {
        return this.sendRequest(this.baseUrl + url, 'patch', body)
            .map((res) => {
                return res as Object
            })
            .toPromise();
    }
    sendRequest(url, type, body, params = null): Observable<any> {
        return this.httpClient[type](url, { params: params }, body)
    }
}

Если я получаю ошибку HTTP (например, 404), я получаю неприятное консольное сообщение: Ошибка ERROR: Uncaught (в обещании): [object Object] из core.es5.js Как мне поступить с этим в моем случае?


person LastTribunal    schedule 03.09.2017    source источник


Ответы (11)


У вас есть несколько вариантов, в зависимости от ваших потребностей. Если вы хотите обрабатывать ошибки для каждого запроса, добавьте catch в свой запрос. Если вы хотите добавить глобальное решение, используйте HttpInterceptor.

Откройте здесь рабочий демонстрационный плункер, чтобы найти решения, указанные ниже.

tl;dr

В простейшем случае вам просто нужно добавить .catch() или .subscribe(), например:

import 'rxjs/add/operator/catch'; // don't forget this, or you'll get a runtime error
this.httpClient
      .get("data-url")
      .catch((err: HttpErrorResponse) => {
        // simple logging, but you can do a lot more, see below
        console.error('An error occurred:', err.error);
      });

// or
this.httpClient
      .get("data-url")
      .subscribe(
        data => console.log('success', data),
        error => console.log('oops', error)
      );

Но об этом подробнее см. Ниже.


Метод (локальное) решение: записать ошибку и вернуть отклик

Если вам нужно обрабатывать ошибки только в одном месте, вы можете использовать catch и вернуть значение по умолчанию (или пустой ответ) вместо полного отказа. Вам также не нужен .map только для приведения, вы можете использовать универсальную функцию. Источник: Angular.io - получение сведений об ошибке.

Итак, общий метод .get() будет выглядеть примерно так:

import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/empty';
import 'rxjs/add/operator/retry'; // don't forget the imports

@Injectable()
export class DataService {
    baseUrl = 'http://localhost';
    constructor(private httpClient: HttpClient) { }

    // notice the <T>, making the method generic
    get<T>(url, params): Observable<T> {
      return this.httpClient
          .get<T>(this.baseUrl + url, {params})
          .retry(3) // optionally add the retry
          .catch((err: HttpErrorResponse) => {

            if (err.error instanceof Error) {
              // A client-side or network error occurred. Handle it accordingly.
              console.error('An error occurred:', err.error.message);
            } else {
              // The backend returned an unsuccessful response code.
              // The response body may contain clues as to what went wrong,
              console.error(`Backend returned code ${err.status}, body was: ${err.error}`);
            }

            // ...optionally return a default fallback value so app can continue (pick one)
            // which could be a default value
            // return Observable.of<any>({my: "default value..."});
            // or simply an empty observable
            return Observable.empty<T>();
          });
     }
}

Обработка ошибки позволит приложению продолжить работу, даже если служба по URL-адресу находится в плохом состоянии.

Это решение для каждого запроса хорошо в основном, когда вы хотите вернуть конкретный ответ по умолчанию для каждого метода. Но если вас интересует только отображение ошибок (или глобальный ответ по умолчанию), лучшим решением будет использование перехватчика, как описано ниже.

Запустите здесь рабочий демонстрационный плункер.


Расширенное использование: перехват всех запросов или ответов

Еще раз, руководство по Angular.io показывает:

Главная особенность @angular/common/http - это перехват, способность объявлять перехватчики, которые находятся между вашим приложением и серверной частью. Когда ваше приложение делает запрос, перехватчики преобразуют его перед отправкой на сервер, а перехватчики могут преобразовать ответ на обратном пути до того, как ваше приложение его увидит. Это полезно для всего, от аутентификации до регистрации.

Что, конечно, можно использовать для обработки ошибок очень простым способом (здесь демонстрационный плункер ):

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpResponse,
         HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/of';
import 'rxjs/add/observable/empty';
import 'rxjs/add/operator/retry'; // don't forget the imports

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request)
      .catch((err: HttpErrorResponse) => {

        if (err.error instanceof Error) {
          // A client-side or network error occurred. Handle it accordingly.
          console.error('An error occurred:', err.error.message);
        } else {
          // The backend returned an unsuccessful response code.
          // The response body may contain clues as to what went wrong,
          console.error(`Backend returned code ${err.status}, body was: ${err.error}`);
        }

        // ...optionally return a default fallback value so app can continue (pick one)
        // which could be a default value (which has to be a HttpResponse here)
        // return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));
        // or simply an empty observable
        return Observable.empty<HttpEvent<any>>();
      });
  }
}

Предоставление вашего перехватчика: простое объявление HttpErrorInterceptor выше не заставляет ваше приложение его использовать. Вам необходимо подключить его к модулю приложения, указав его как перехватчик, следующее:

import { NgModule } from '@angular/core';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { HttpErrorInterceptor } from './path/http-error.interceptor';

@NgModule({
  ...
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: HttpErrorInterceptor,
    multi: true,
  }],
  ...
})
export class AppModule {}

Примечание. Если у вас есть и перехватчик ошибок, и некоторая локальная обработка ошибок, естественно, вероятно, что локальная обработка ошибок никогда не будет запущена, поскольку ошибка всегда будет обрабатывается перехватчиком до, когда он достигает локальной обработки ошибок.

Запустите здесь рабочий демонстрационный плункер.

person acdcjunior    schedule 03.09.2017
comment
ну, если он хочет быть полностью навороченным, он оставит свою службу полностью чистой: return this.httpClient.get<type>(...). а затем иметь catch... где-нибудь вне службы, где он фактически ее потребляет, потому что именно там он будет строить поток наблюдаемых объектов и может справиться с этим лучше всего. - person dee zg; 03.09.2017
comment
return Observable.of (e); работал у меня Таким образом, я могу отложить дескриптор исключения для вызывающего - person LastTribunal; 03.09.2017
comment
Я согласен, возможно, оптимальным решением было бы иметь клиента Promise<Object> (вызывающего методы DataService) для обработки ошибки. Пример: this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. Выберите то, что понятнее вам и пользователям вашего сервиса. - person acdcjunior; 03.09.2017
comment
Это не компилируется: return Observable.of({my: "default value..."}); Выдает ошибку | ... 'не может быть присвоено типу' HttpEvent ‹any› '. - person Yakov Fain; 30.12.2017
comment
@YakovFain Если вам нужно значение по умолчанию в перехватчике, оно должно быть HttpEvent, например HttpResponse. Так, например, вы можете использовать: return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));. Я обновил ответ, чтобы прояснить этот момент. Кроме того, я создал рабочий демонстрационный плункер, чтобы показать, что все работает: plnkr.co/edit/ulFGp4VMzrba6DJepqq = предварительный просмотр - person acdcjunior; 30.12.2017
comment
@acdcjunior, ты подарок, который не перестает дарить :) - person LastTribunal; 07.04.2018
comment
Спасибо за ответ, ${err.error} дает мне: body was: [object Object] Вы хоть что-нибудь знаете об этом? - person ValRob; 08.04.2018
comment
Вот подсказка ссылка - person ValRob; 08.04.2018
comment
Просто, черт возьми, иди с этим - person Yoraco Gonzales; 25.11.2018
comment
Этот ответ требует большого обновления с точки зрения импорта для rxjs - person Paul Kruger; 22.03.2019
comment
@PaulKruger, не могли бы вы указать мне какие-нибудь ресурсы для этого, чтобы я мог обновить ответ? Я не использовал новейшие rxjs. - person acdcjunior; 22.03.2019
comment
Проверьте ответ ниже от @MegaCasper, я не на 100% уверен, что нужно изменить, но я знаю, что ваш код, к сожалению, очень устарел :) - person Paul Kruger; 25.03.2019
comment
@acdcjunior, не могли бы вы обновить свой ответ, catch () устарел, в настоящее время они используют catchError () и не забыли добавить канал для операторов цепочки: D - person Rebai Ahmed; 14.07.2021
comment
@RebaiAhmed, есть ли у вас еще подсказки для этого? Я обновлю ответ, но в последнее время не использовал Angular - person acdcjunior; 14.07.2021

Разрешите мне обновить ответ acdcjunior об использовании HttpInterceptor с новейшими функциями RxJs (v.6).

import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpErrorResponse,
  HttpHandler,
  HttpEvent,
  HttpResponse
} from '@angular/common/http';

import { Observable, EMPTY, throwError, of } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class HttpErrorInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.error instanceof Error) {
          // A client-side or network error occurred. Handle it accordingly.
          console.error('An error occurred:', error.error.message);
        } else {
          // The backend returned an unsuccessful response code.
          // The response body may contain clues as to what went wrong,
          console.error(`Backend returned code ${error.status}, body was: ${error.error}`);
        }

        // If you want to return a new response:
        //return of(new HttpResponse({body: [{name: "Default value..."}]}));

        // If you want to return the error on the upper level:
        //return throwError(error);

        // or just return nothing:
        return EMPTY;
      })
    );
  }
}
person MegaCasper    schedule 19.11.2018
comment
Это должно получить больше голосов. Ответ acdcjunior непригоден для использования на сегодняшний день - person Paul Kruger; 22.03.2019

С появлением HTTPClient API не только был заменен Http API, но и был добавлен новый HttpInterceptor API.

AFAIK одна из его целей - добавить поведение по умолчанию ко всем исходящим запросам и входящим ответам HTTP.

Предполагая, что вы хотите добавить поведение обработки ошибок по умолчанию, добавление .catch() ко всем вашим возможным методам http.get / post / etc смехотворно сложно поддерживать.

Это можно сделать следующим образом, например, используя HttpInterceptor:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest, HttpErrorResponse, HTTP_INTERCEPTORS } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import { _throw } from 'rxjs/observable/throw';
import 'rxjs/add/operator/catch';

/**
 * Intercepts the HTTP responses, and in case that an error/exception is thrown, handles it
 * and extract the relevant information of it.
 */
@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
    /**
     * Intercepts an outgoing HTTP request, executes it and handles any error that could be triggered in execution.
     * @see HttpInterceptor
     * @param req the outgoing HTTP request
     * @param next a HTTP request handler
     */
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(req)
            .catch(errorResponse => {
                let errMsg: string;
                if (errorResponse instanceof HttpErrorResponse) {
                    const err = errorResponse.message || JSON.stringify(errorResponse.error);
                    errMsg = `${errorResponse.status} - ${errorResponse.statusText || ''} Details: ${err}`;
                } else {
                    errMsg = errorResponse.message ? errorResponse.message : errorResponse.toString();
                }
                return _throw(errMsg);
            });
    }
}

/**
 * Provider POJO for the interceptor
 */
export const ErrorInterceptorProvider = {
    provide: HTTP_INTERCEPTORS,
    useClass: ErrorInterceptor,
    multi: true,
};

// app.module.ts

import { ErrorInterceptorProvider } from 'somewhere/in/your/src/folder';

@NgModule({
   ...
   providers: [
    ...
    ErrorInterceptorProvider,
    ....
   ],
   ...
})
export class AppModule {}

Дополнительная информация для OP: вызов http.get / post / etc без строгого типа - не оптимальное использование API. Ваш сервис должен выглядеть так:

// These interfaces could be somewhere else in your src folder, not necessarily in your service file
export interface FooPost {
 // Define the form of the object in JSON format that your 
 // expect from the backend on post
}

export interface FooPatch {
 // Define the form of the object in JSON format that your 
 // expect from the backend on patch
}

export interface FooGet {
 // Define the form of the object in JSON format that your 
 // expect from the backend on get
}

@Injectable()
export class DataService {
    baseUrl = 'http://localhost'
    constructor(
        private http: HttpClient) {
    }

    get(url, params): Observable<FooGet> {

        return this.http.get<FooGet>(this.baseUrl + url, params);
    }

    post(url, body): Observable<FooPost> {
        return this.http.post<FooPost>(this.baseUrl + url, body);
    }

    patch(url, body): Observable<FooPatch> {
        return this.http.patch<FooPatch>(this.baseUrl + url, body);
    }
}

Еще одно плохое решение - вернуть Promises из ваших методов обслуживания вместо Observables.

И еще один совет: если вы используете скрипт TYPE, начните использовать его типовую часть. Вы теряете одно из самых больших преимуществ языка: знание типа значения, с которым вы имеете дело.

Если вам нужен, на мой взгляд, хороший пример сервиса angular, взгляните на следующую суть .

person Jota.Toledo    schedule 03.09.2017

Для Angular 6+ .catch не работает напрямую с Observable. Вы должны использовать

.pipe(catchError(this.errorHandler))

Ниже код:

import { IEmployee } from './interfaces/employee';
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class EmployeeService {

  private url = '/assets/data/employee.json';

  constructor(private http: HttpClient) { }

  getEmployees(): Observable<IEmployee[]> {
    return this.http.get<IEmployee[]>(this.url)
                    .pipe(catchError(this.errorHandler));  // catch error
  }

  /** Error Handling method */

  errorHandler(error: HttpErrorResponse) {
    if (error.error instanceof ErrorEvent) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', error.error.message);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong,
      console.error(
        `Backend returned code ${error.status}, ` +
        `body was: ${error.error}`);
    }
    // return an observable with a user-facing error message
    return throwError(
      'Something bad happened; please try again later.');
  }
}

Дополнительные сведения см. В Руководстве по Angular для Http.

person Udith Indrakantha    schedule 03.05.2019
comment
Это единственный ответ, который у меня сработал. Остальные выдают ошибку: Тип Observable ‹unknown› не может быть назначен типу Observable ‹HttpEvent ‹any››. - person King Arthur the Third; 18.02.2020

Довольно просто (по сравнению с тем, как это было сделано с предыдущим API).

Источник из (скопируйте и вставьте) официального руководства по Angular

 http
  .get<ItemsResponse>('/api/items')
  .subscribe(
    // Successful responses call the first callback.
    data => {...},
    // Errors will call this callback instead:
    err => {
      console.log('Something went wrong!');
    }
  );
person Tomer    schedule 15.11.2017

Служба обработки ошибок HttpClient Angular 8 Пример

введите описание изображения здесь

api.service.ts

    import { Injectable } from '@angular/core';
    import { HttpClient, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
    import { Student } from '../model/student';
    import { Observable, throwError } from 'rxjs';
    import { retry, catchError } from 'rxjs/operators';

    @Injectable({
      providedIn: 'root'
    })
    export class ApiService {

      // API path
      base_path = 'http://localhost:3000/students';

      constructor(private http: HttpClient) { }

      // Http Options
      httpOptions = {
        headers: new HttpHeaders({
          'Content-Type': 'application/json'
        })
      }

      // Handle API errors
      handleError(error: HttpErrorResponse) {
        if (error.error instanceof ErrorEvent) {
          // A client-side or network error occurred. Handle it accordingly.
          console.error('An error occurred:', error.error.message);
        } else {
          // The backend returned an unsuccessful response code.
          // The response body may contain clues as to what went wrong,
          console.error(
            `Backend returned code ${error.status}, ` +
            `body was: ${error.error}`);
        }
        // return an observable with a user-facing error message
        return throwError(
          'Something bad happened; please try again later.');
      };


      // Create a new item
      createItem(item): Observable<Student> {
        return this.http
          .post<Student>(this.base_path, JSON.stringify(item), this.httpOptions)
          .pipe(
            retry(2),
            catchError(this.handleError)
          )
      }

     ........
     ........

    }
person Code Spy    schedule 07.08.2019

Вероятно, вы захотите иметь что-то вроде этого:

this.sendRequest(...)
.map(...)
.catch((err) => {
//handle your error here
})

Это также во многом зависит от того, как вы используете свой сервис, но это основной случай.

person dee zg    schedule 03.09.2017

После ответа @acdcjunior вот как я его реализовал

услуга:

  get(url, params): Promise<Object> {

            return this.sendRequest(this.baseUrl + url, 'get', null, params)
                .map((res) => {
                    return res as Object
                }).catch((e) => {
                    return Observable.of(e);
                })
                .toPromise();
        }

звонящий:

this.dataService.get(baseUrl, params)
            .then((object) => {
                if(object['name'] === 'HttpErrorResponse') {
                            this.error = true;
                           //or any handle
                } else {
                    this.myObj = object as MyClass 
                }
           });
person LastTribunal    schedule 03.09.2017
comment
Почему вы используете Promise вместо Observable! - person Rebai Ahmed; 14.07.2021

Если вы не можете обнаружить ошибки ни с одним из предложенных здесь решений, возможно, сервер не обрабатывает запросы CORS.

В этом случае Javascript, а тем более Angular, может получить доступ к информации об ошибке.

Ищите в консоли предупреждения, содержащие CORB или Cross-Origin Read Blocking.

Кроме того, изменился синтаксис для обработки ошибок (как описано в каждом другом ответе). Теперь вы используете конвейерные операторы, например:

this.service.requestsMyInfo(payload).pipe(
    catcheError(err => {
        // handle the error here.
    })
);
person Kevin Beal    schedule 20.08.2018

Используя Interceptor, вы можете поймать ошибку. Ниже приведен код:

@Injectable()
export class ResponseInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    //Get Auth Token from Service which we want to pass thr service call
    const authToken: any = `Bearer ${sessionStorage.getItem('jwtToken')}`
    // Clone the service request and alter original headers with auth token.
    const authReq = req.clone({
      headers: req.headers.set('Content-Type', 'application/json').set('Authorization', authToken)
    });

    const authReq = req.clone({ setHeaders: { 'Authorization': authToken, 'Content-Type': 'application/json'} });

    // Send cloned request with header to the next handler.
    return next.handle(authReq).do((event: HttpEvent<any>) => {
      if (event instanceof HttpResponse) {
        console.log("Service Response thr Interceptor");
      }
    }, (err: any) => {
      if (err instanceof HttpErrorResponse) {
        console.log("err.status", err);
        if (err.status === 401 || err.status === 403) {
          location.href = '/login';
          console.log("Unauthorized Request - In case of Auth Token Expired");
        }
      }
    });
  }
}

Вы можете предпочесть этот блог ..дал простой пример.

person Prashant M Bhavsar    schedule 12.03.2019

Хуже всего то, что у вас нет приличной трассировки стека, которую вы просто не можете сгенерировать с помощью HttpInterceptor (надеюсь, что исправят). Все, что вы получаете, - это загрузка зоны и бесполезного раздувания rxjs, а не строка или класс, которые сгенерировали ошибку.

Для этого вам нужно будет сгенерировать стек в расширенном HttpClient, поэтому не рекомендуется делать это в производственной среде.

/**
 * Extended HttpClient that generates a stack trace on error when not in a production build.
 */
@Injectable()
export class TraceHttpClient extends HttpClient {
  constructor(handler: HttpHandler) {
    super(handler);
  }

  request(...args: [any]): Observable<any> {
    const stack = environment.production ? null : Error().stack;
    return super.request(...args).pipe(
      catchError((err) => {
        // tslint:disable-next-line:no-console
        if (stack) console.error('HTTP Client error stack\n', stack);
        return throwError(err);
      })
    );
  }
}
person jenson-button-event    schedule 17.10.2020