Attraper des erreurs dans Angular HttpClient

114

J'ai un service de données qui ressemble à ceci:

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

Si j'obtiens une erreur HTTP (c.-à-d. 404), j'obtiens un message de console désagréable: ERREUR Erreur: Uncaught (promis): [objet objet] de core.es5.js Comment puis-je gérer cela dans mon cas?

LastTribunal
la source

Réponses:

231

Vous avez quelques options, en fonction de vos besoins. Si vous souhaitez gérer les erreurs sur une base par demande, ajoutez un catchà votre demande. Si vous souhaitez ajouter une solution globale, utilisez HttpInterceptor.

Ouvrez ici le plunker de démonstration fonctionnel pour les solutions ci-dessous.

tl; dr

Dans le cas le plus simple, il vous suffira d'ajouter un .catch()ou un .subscribe(), comme:

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

Mais il y a plus de détails à ce sujet, voir ci-dessous.


Solution de méthode (locale): erreur de journal et retour de réponse de secours

Si vous devez gérer des erreurs à un seul endroit, vous pouvez utiliser catchet renvoyer une valeur par défaut (ou une réponse vide) au lieu d'échouer complètement. Vous n'avez pas non plus besoin de .mapsimplement cast, vous pouvez utiliser une fonction générique. Source: Angular.io - Obtenir les détails de l'erreur .

Donc, une .get()méthode générique , serait comme:

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 gestion de l'erreur permettra à votre application de continuer même lorsque le service à l'URL est en mauvais état.

Cette solution par demande convient surtout lorsque vous souhaitez renvoyer une réponse par défaut spécifique à chaque méthode. Mais si vous ne vous souciez que de l'affichage des erreurs (ou si vous avez une réponse globale par défaut), la meilleure solution est d'utiliser un intercepteur, comme décrit ci-dessous.

Exécutez le plunker de démonstration fonctionnel ici .


Utilisation avancée: intercepter toutes les demandes ou réponses

Une fois de plus, le guide Angular.io montre:

Une caractéristique majeure de @angular/common/httpest l'interception, la possibilité de déclarer des intercepteurs qui se trouvent entre votre application et le backend. Lorsque votre application fait une demande, les intercepteurs la transforment avant de l'envoyer au serveur, et les intercepteurs peuvent transformer la réponse à son retour avant que votre application ne la voie. Ceci est utile pour tout, de l'authentification à la journalisation.

Ce qui, bien sûr, peut être utilisé pour gérer les erreurs de manière très simple ( démo plunker ici ):

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

Fournir votre intercepteur: le simple fait de déclarer ce qui HttpErrorInterceptorprécède ne provoque pas son utilisation par votre application. Vous devez le connecter dans votre module d'application en le fournissant en tant qu'intercepteur, comme suit:

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

Remarque: si vous disposez à la fois d' un intercepteur d'erreur et d'une gestion des erreurs locale, il est bien entendu probable qu'aucune gestion d'erreur locale ne sera jamais déclenchée, car l'erreur sera toujours gérée par l'intercepteur avant d' atteindre la gestion d'erreur locale.

Exécutez le plunker de démonstration fonctionnel ici .

acdcjunior
la source
2
Eh bien, s'il veut être de fantaisie pleinement qu'il quitterait son service tout à fait clair: return this.httpClient.get<type>(...). et ensuite avoir catch...quelque part hors du service où il le consomme réellement parce que c'est là qu'il construira un flux d'observables et qu'il pourra le gérer le mieux.
dee zg
1
Je suis d'accord, peut-être qu'une solution optimale serait d'avoir le Promise<Object>client de 's (l'appelant des DataServiceméthodes de') pour gérer l'erreur. Exemple: this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. Choisissez ce qui est le plus clair pour vous et les utilisateurs de votre service.
acdcjunior
1
Cela ne compile pas: return Observable.of({my: "default value..."}); cela donne une erreur "| ... 'n'est pas assignable au type' HttpEvent <any> '."
Yakov Fain
1
@YakovFain Si vous voulez une valeur par défaut dans l'intercepteur, elle doit être a HttpEvent, telle que a HttpResponse. Ainsi, par exemple, vous pouvez utiliser: return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));. J'ai mis à jour la réponse pour clarifier ce point. En outre, j'ai créé un plunker de démonstration fonctionnel pour montrer que tout fonctionne: plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview
acdcjunior
1
@acdcjunior, vous êtes un cadeau qui continue à donner :)
LastTribunal
67

Permettez - moi s'il vous plaît mettre à jour le acdcjunior la réponse « sur l' utilisation HttpInterceptor avec les dernières fonctionnalités 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;
      })
    );
  }
}
MegaCasper
la source
11
Cela doit être davantage voté. La réponse d'acdcjunior est inutilisable à partir d'aujourd'hui
Paul Kruger
48

Avec l'arrivée de l' HTTPClientAPI, non seulement l' HttpAPI a été remplacée, mais une nouvelle a été ajoutée, l' HttpInterceptorAPI.

AFAIK l'un de ses objectifs est d'ajouter un comportement par défaut à toutes les requêtes HTTP sortantes et réponses entrantes.

Donc, en supposant que vous vouliez ajouter un comportement de gestion des erreurs par défaut , l'ajout .catch()à toutes vos méthodes http.get / post / etc possibles est ridiculement difficile à maintenir.

Cela pourrait être fait de la manière suivante à titre d'exemple en utilisant un 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 {}

Quelques informations supplémentaires pour OP: appeler http.get / post / etc sans un type fort n'est pas une utilisation optimale de l'API. Votre service devrait ressembler à ceci:

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

Revenir Promisesde vos méthodes de service au lieu de Observablesest une autre mauvaise décision.

Et un conseil supplémentaire: si vous utilisez le script TYPE , commencez à utiliser la partie type de celui-ci. Vous perdez l'un des plus gros avantages du langage: connaître le type de valeur à laquelle vous avez affaire.

Si vous voulez un, à mon avis, un bon exemple de service angulaire, jetez un œil à l'essentiel suivant .

Jota.Toledo
la source
Les commentaires ne sont pas destinés à une discussion approfondie; cette conversation a été déplacée vers le chat .
deceze
Je suppose que cela devrait être this.http.get()etc. et non this.get()etc. dans DataService?
displayname
La réponse sélectionnée semble maintenant plus complète.
Chris Haines
9

Assez simple (par rapport à la façon dont cela a été fait avec l'API précédente).

Source de (copier-coller) le guide officiel 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!');
    }
  );
Tomer
la source
9

Pour Angular 6+, .catch ne fonctionne pas directement avec Observable. Vous devez utiliser

.pipe(catchError(this.errorHandler))

Ci-dessous le code:

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

Pour plus de détails, reportez-vous au Guide angulaire pour Http

Udith Indrakantha
la source
1
C'est la seule réponse qui a fonctionné pour moi. Les autres donnent une erreur de: "Le type 'Observable <unknown>' n'est pas assignable au type 'Observable <HttpEvent <any>>".
King Arthur the Third le
5

Exemple de service de gestion d'erreurs Angular 8 HttpClient

entrez la description de l'image ici

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

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

    }
Espion de code
la source
2

Vous voulez probablement avoir quelque chose comme ça:

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

Cela dépend aussi fortement de la manière dont vous utilisez votre service, mais c'est le cas de base.

dee zg
la source
1

Suite à la réponse @acdcjunior, voici comment je l'ai implémentée

un service:

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

votre interlocuteur:

this.dataService.get(baseUrl, params)
            .then((object) => {
                if(object['name'] === 'HttpErrorResponse') {
                            this.error = true;
                           //or any handle
                } else {
                    this.myObj = object as MyClass 
                }
           });
LastTribunal
la source
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)));
}
soleil sreng
la source
0

Si vous ne parvenez pas à détecter les erreurs avec l'une des solutions fournies ici, il se peut que le serveur ne gère pas les requêtes CORS.

Dans ce cas, Javascript, encore moins Angular, peut accéder aux informations d'erreur.

Recherchez les avertissements dans votre console qui incluent CORBou Cross-Origin Read Blocking.

En outre, la syntaxe a changé pour la gestion des erreurs (comme décrit dans toutes les autres réponses). Vous utilisez maintenant des opérateurs canalisables, comme ceci:

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

En utilisant Interceptor, vous pouvez détecter une erreur. Ci-dessous le code:

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

Vous pouvez préférer ce blog ... avec un exemple simple pour cela.

Prashant M Bhavsar
la source