Rilevamento degli errori in Angular HttpClient


114

Ho un servizio dati simile a questo:

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

Se ricevo un errore HTTP (es. 404), ricevo un brutto messaggio sulla console: ERRORE Errore: Uncaught (in promessa): [Object Object] da core.es5.js Come lo gestisco nel mio caso?

Risposte:


231

Hai alcune opzioni, a seconda delle tue esigenze. Se desideri gestire gli errori in base alla richiesta, aggiungi un catchalla tua richiesta. Se vuoi aggiungere una soluzione globale, usa HttpInterceptor.

Apri qui il plunker demo funzionante per le soluzioni seguenti.

tl; dr

Nel caso più semplice, dovrai solo aggiungere un .catch()o un .subscribe(), come:

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

Ma ci sono più dettagli su questo, vedi sotto.


Metodo (locale) soluzione: registrare l'errore e restituire la risposta di fallback

Se è necessario gestire gli errori in un solo punto, è possibile utilizzare catche restituire un valore predefinito (o una risposta vuota) invece di fallire completamente. Inoltre, non hai bisogno del .mapsolo cast, puoi usare una funzione generica. Fonte: Angular.io - Recupero dei dettagli sugli errori .

Quindi, un .get()metodo generico , sarebbe come:

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

La gestione dell'errore consentirà alla tua app di continuare anche quando il servizio all'URL è in cattive condizioni.

Questa soluzione per richiesta è utile soprattutto quando si desidera restituire una risposta predefinita specifica a ciascun metodo. Ma se ti interessa solo la visualizzazione degli errori (o hai una risposta predefinita globale), la soluzione migliore è usare un intercettore, come descritto di seguito.

Esegui qui il plunker demo funzionante .


Utilizzo avanzato: intercettazione di tutte le richieste o risposte

Ancora una volta, la guida Angular.io mostra:

Una delle principali caratteristiche di @angular/common/httpè l'intercettazione, la capacità di dichiarare gli intercettori che si trovano tra la tua applicazione e il backend. Quando la tua applicazione effettua una richiesta, gli intercettori la trasformano prima di inviarla al server e gli intercettori possono trasformare la risposta sulla via del ritorno prima che la tua applicazione la veda. Questo è utile per tutto, dall'autenticazione alla registrazione.

Che, ovviamente, può essere utilizzato per gestire gli errori in modo molto semplice ( demo plunker qui ):

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

Fornire il tuo intercettore: la semplice dichiarazione di quanto HttpErrorInterceptorsopra non fa sì che la tua app lo utilizzi. È necessario collegarlo al modulo dell'app fornendolo come intercettore, come segue:

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

Nota: se si dispone sia di un intercettatore di errori che di una gestione degli errori locale, naturalmente, è probabile che non venga mai attivata alcuna gestione degli errori locali, poiché l'errore verrà sempre gestito dall'interceptor prima che raggiunga la gestione degli errori locale.

Esegui qui il plunker demo funzionante .


2
bene, se vuole essere pienamente fantasia le avrebbe lasciato il suo servizio completamente chiara: return this.httpClient.get<type>(...). e poi avere un catch...posto fuori servizio dove lo consuma effettivamente perché è lì che costruirà il flusso di osservabili e potrà gestirlo meglio.
dee zg

1
Sono d'accordo, forse una soluzione ottimale sarebbe avere il Promise<Object>client di (il chiamante dei DataServicemetodi di) per gestire l'errore. Esempio: this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. Scegli ciò che è più chiaro per te e per gli utenti del tuo servizio.
acdcjunior

1
Questo non si compila: return Observable.of({my: "default value..."}); restituisce un errore "| ... 'non è assegnabile al tipo' HttpEvent <any> '."
Yakov Fain

1
@YakovFain Se si desidera un valore predefinito nell'interceptor, deve essere a HttpEvent, ad esempio a HttpResponse. Così, per esempio, è possibile utilizzare: return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));. Ho aggiornato la risposta per chiarire questo punto. Inoltre, ho creato un plunker demo funzionante per mostrare che tutto funziona: plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview
acdcjunior

1
@acdcjunior, sei un regalo che continua a dare :)
LastTribunal

67

Lasciatemi Si prega di aggiornare l'acdcjunior risposta s' sull'utilizzo HttpInterceptor con le ultime caratteristiche 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
Questo deve essere votato di più. La risposta di acdcjunior è inutilizzabile per oggi
Paul Kruger

48

Con l'arrivo HTTPClientdell'API, non solo è stata Httpsostituita l' API, ma ne è stata aggiunta una nuova, l' HttpInterceptorAPI.

Per quanto ne so, uno dei suoi obiettivi è aggiungere un comportamento predefinito a tutte le richieste HTTP in uscita e le risposte in arrivo.

Quindi, supponendo che tu voglia aggiungere un comportamento di gestione degli errori predefinito , aggiungere .catch()a tutti i tuoi possibili metodi http.get / post / etc è ridicolmente difficile da mantenere.

Questo potrebbe essere fatto nel modo seguente come esempio utilizzando a 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 {}

Alcune informazioni extra per OP: chiamare http.get / post / etc senza un tipo forte non è un uso ottimale dell'API. Il tuo servizio dovrebbe essere simile a questo:

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

Restituire Promisesdai tuoi metodi di servizio invece che Observablesè un'altra cattiva decisione.

E un consiglio in più: se stai usando lo script TYPE , inizia a usarne la parte tipo. Perdi uno dei maggiori vantaggi della lingua: conoscere il tipo di valore con cui hai a che fare.

Se vuoi un, a mio avviso, un buon esempio di servizio angolare, dai un'occhiata alla seguente sintesi .


I commenti non sono per discussioni estese; questa conversazione è stata spostata nella chat .
inganno

Presumo che questo dovrebbe essere this.http.get()ecc. E non this.get()ecc. In DataService?
displayname

La risposta selezionata sembra ora essere più completa.
Chris Haines

9

Abbastanza semplice (rispetto a come è stato fatto con l'API precedente).

Fonte (copia e incolla) della guida ufficiale di 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

Per Angular 6+, .catch non funziona direttamente con Observable. Devi usare

.pipe(catchError(this.errorHandler))

Sotto il codice:

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

Per maggiori dettagli, fare riferimento alla Guida angolare per Http


1
Questa è l'unica risposta che ha funzionato per me. Gli altri danno un errore di: "Il tipo" Observable <unknown> "non è assegnabile al tipo" Observable <HttpEvent <any>> ".
King Arthur il terzo

5

Esempio di servizio di gestione degli errori HttpClient di Angular 8

inserisci qui la descrizione dell'immagine

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

Probabilmente vorrai avere qualcosa del genere:

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

Dipende molto anche da come usi il tuo servizio, ma questo è il caso di base.


1

Seguendo la risposta di @acdcjunior, è così che l'ho implementata

servizio:

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

chiamante:

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

Se non riesci a rilevare errori con nessuna delle soluzioni fornite qui, è possibile che il server non stia gestendo le richieste CORS.

In tal caso, Javascript, molto meno Angular, può accedere alle informazioni sull'errore.

Cerca nella tua console gli avvisi che includono CORBo Cross-Origin Read Blocking.

Inoltre, la sintassi è cambiata per la gestione degli errori (come descritto in ogni altra risposta). Ora usi gli operatori pipe in grado, in questo modo:

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

0

Utilizzando Interceptor è possibile rilevare l'errore. Di seguito è riportato il codice:

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

Puoi preferire questo blog ..dato un semplice esempio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.