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

@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), я получаю неприятное консольное сообщение: Ошибка ОШИБКИ: невыполненная (в обещании): [объект объекта] из core.es5.js Как мне справиться с этим в моем случае?

114
LastTribunal 3 Сен 2017 в 05:26

11 ответов

Лучший ответ

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

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

ТЛ ; др

В простейшем случае вам просто нужно добавить .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 {}

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

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

231
acdcjunior 30 Дек 2017 в 19:38

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

enter image description here

< Сильный > 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)
          )
      }

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

    }
5
Code Spy 7 Авг 2019 в 15:53

Довольно просто (по сравнению с тем, как это было сделано с предыдущим 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!');
    }
  );
9
Tomer 15 Ноя 2017 в 23:01
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

const PASSENGER_API = 'api/passengers';

getPassengers(): Observable<Passenger[]> {
  return this.http
    .get<Passenger[]>(PASSENGER_API)
    .pipe(catchError((error: HttpErrorResponse) => throwError(error)));
}
1
Paul Rooney 31 Июл 2019 в 05:51

После ответа @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 
                }
           });
1
LastTribunal 3 Сен 2017 в 04:02

Для 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.');
  }
}

Подробнее см. В Угловом руководстве по Http

9
Paul Rooney 31 Июл 2019 в 05:49

С помощью 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");
        }
      }
    });
  }
}

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

0
Paul Rooney 31 Июл 2019 в 05:51

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

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 , то начните использовать его часть type. Вы теряете одно из самых больших преимуществ языка: знать тип значения, с которым вы имеете дело.

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

48
BSMP 15 Фев 2018 в 20:51

Позвольте мне обновить 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;
      })
    );
  }
}
67
MegaCasper 19 Ноя 2018 в 17:20

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

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

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

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

this.service.requestsMyInfo(payload).pipe(
    catcheError(err => {
        // handle the error here.
    })
);
0
Kevin Beal 20 Авг 2018 в 14:57

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

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

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

2
dee zg 3 Сен 2017 в 02:51