Помилки лову в кутовому HttpClient


114

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

@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 Як я обробляю це в моєму випадку?

Відповіді:


231

У вас є кілька варіантів, залежно від ваших потреб. Якщо ви хочете обробляти помилки на основі запиту, додайте 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>();
          });
     }
}

Поводження з помилкою дозволить вашій програмі продовжувати роботу, навіть коли служба за адресою знаходиться в поганому стані.

Це рішення на запит добре, головним чином, коли ви хочете повернути конкретну відповідь за замовчуванням для кожного методу. Але якщо ви дбаєте лише про відображення помилок (або маєте глобальну відповідь за замовчуванням), кращим рішенням буде використання перехоплювача, як описано нижче.

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


Розширене використання: перехоплення всіх запитів чи відповідей

Ще раз, посібник 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 {}

Примітка: Якщо у вас є як перехоплювач помилок, так і деякі локальні поводження з помилками, природно, ймовірно, що локальне поводження з помилками ніколи не буде спрацьовано, оскільки помилка завжди буде оброблятися перехоплювачем до того, як воно дістанеться до локальної обробки помилок.

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


2
добре, якщо він хоче бути повністю здається , він залишив би свою службу цілком ясно: return this.httpClient.get<type>(...). а потім catch...десь поза службою, де він насправді споживає її, тому що там він буде будувати спостережний потік і може впоратися з цим найкраще.
dee zg

1
Я погоджуюся, можливо, оптимальним рішенням було б мати Promise<Object>клієнта (абонента DataServiceметодів), щоб обробити помилку. Приклад: this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. Виберіть те, що вам зрозуміліше та користувачам вашої послуги.
acdcjunior

1
Це не компілюється: return Observable.of({my: "default value..."}); це дає помилку "| ..." не призначається для введення "HttpEvent <any>". "
Яків Файн

1
@YakovFain Якщо ви хочете значення за замовчуванням у перехоплювачі, воно повинно бути a HttpEvent, таким як HttpResponse. Так, наприклад, ви можете використовувати: return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));. Я оновив відповідь, щоб зробити це зрозумілим. Також я створив робочий демонстраційний планкер, щоб показати все, що працює: plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview
acdcjunior

1
@acdcjunior, ти подарунок, який продовжує дарувати :)
LastTribunal

67

Дозвольте мені оновити відповідь 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;
      })
    );
  }
}

11
На це потрібно більше звертати увагу. Відповідь acdcjunior сьогодні непридатна
Пол Крюгер

48

З приходом HTTPClientAPI не тільки було Httpзамінено API, але було додано новий, HttpInterceptorAPI.

Одна з цілей 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 {}

Деякі додаткові відомості для ОП: Виклик http.get / post / тощо без сильного типу не є оптимальним використанням 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 , тоді почніть використовувати його частину. Ви втрачаєте одну з найбільших переваг мови: знати тип значення, з яким ви маєте справу.

Якщо ви хочете, на мою думку, хорошого прикладу кутового обслуговування, погляньте на наступну суть .


Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
деге

Я припускаю, що це повинно бути this.http.get()і т.д., а не this.get()тощо DataService?
displayname

Вибрана відповідь тепер виглядає більш повною.
Кріс Хайнс

9

Досить просто (порівняно з тим, як це було зроблено з попереднім 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

Для Angular 6+, .catch не працює безпосередньо з спостерігається. Ви повинні використовувати

.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


1
Це єдина відповідь, яка працювала на мене. Інші дають помилку: "Тип" Спостережуваний <невідомий> "не можна присвоювати типу" Спостережуваний <HttpEvent <кожен>> ".
Король Артур третій

5

Приклад обслуговування кутових помилок 8 HttpClient

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

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)
          )
      }

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

    }

2

Ви, мабуть, хочете мати щось подібне:

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

Це дуже залежить і від того, як ви користуєтесь послугою, але це основний випадок.


1

Після відповіді @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

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)));
}

0

Якщо ви виявите, що не можете зрозуміти помилки з будь-яким із запропонованих тут рішень, можливо, сервер не обробляє запити CORS.

У цьому випадку Javascript, значно рідше Angular, може отримати доступ до інформації про помилку.

Шукайте на консолі попередження, які містять CORBабо Cross-Origin Read Blocking.

Також синтаксис змінився для помилок обробки (як описано в будь-якій іншій відповіді). Тепер ви використовуєте оператори, що працюють на трубах, наприклад:

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

0

Використовуючи 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");
        }
      }
    });
  }
}

Ви можете віддати перевагу цьому блогу .. простий простий приклад для нього.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.