158 votes

Capture d'erreurs dans Angular HttpClient

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 je reçois une erreur HTTP (c'est-à-dire 404), j'obtiens un méchant message de la console : ERROR Erreur : Inconnu (dans la promesse) : [objet Objet] de core.es5.js Comment dois-je m'y prendre dans mon cas ?

293voto

acdcjunior Points 19898

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

Ouvrir ici le travail de démonstration plunker pour les solutions ci-dessous.

tl;dr

Dans le cas le plus simple, vous avez juste besoin d'ajouter un .catch() ou .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.


Méthode (local) solution: journal d'erreur et le retour de secours réponse

Si vous avez besoin pour gérer les erreurs dans un seul endroit, vous pouvez utiliser catch et de retourner une valeur par défaut (ou de réponse vide) au lieu d'échouer complètement. Vous pouvez aussi ne pas besoin de l' .map seulement de jeter, vous pouvez utiliser une fonction générique. Source: Angulaire.io - Obtenir des Détails de l'Erreur.

Donc, un générique .get() méthode, serait de:

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 manipulation de l'erreur vous permettra d'application à continuer même si le service à l'adresse URL est en mauvais état.

Cette par-requête solution est bonne la plupart du temps quand vous voulez retourner un spécifique de réponse par défaut à chaque méthode. Mais si vous ne se soucient que l'erreur d'affichage (ou qui ont un global de réponse par défaut), la meilleure solution est d'utiliser un intercepteur, comme décrit ci-dessous.

Exécuter le travail de démonstration plunker ici.


Utilisation avancée: Intercepter toutes les requêtes ou réponses

Une fois de plus, Angulaire.io guide montre:

L'une des caractéristiques majeures de l' @angular/common/http est l'interception, la possibilité de déclarer des intercepteurs qui siègent dans l'entre votre application et le backend. Lorsque votre application effectue une demande, les intercepteurs de la transformer avant de les envoyer au serveur, et les intercepteurs peuvent transformer la réponse sur son chemin de retour avant que votre demande ne la voit. C'est utile pour tout, de l'authentification à la connexion.

Ce qui, bien sûr, peut être utilisé pour gérer les erreurs d'une 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>>();
      });
  }
}

En fournissant votre intercepteur: le simple fait de déclarer l' HttpErrorInterceptor ci-dessus n'est pas la cause de votre application pour l'utiliser. Vous avez besoin de fil dans votre application module en fournissant comme un 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 avez à la fois une erreur de l'intercepteur et de la gestion des erreurs, naturellement, il est probable qu'aucune erreur de manipulation ne sera jamais déclenché, depuis que l'erreur sera toujours gérée par l'intercepteur avant qu'il atteigne le local d'erreur de manipulation.

Exécuter le travail de démonstration plunker ici.

2 votes

Eh bien, s'il veut être complètement libre, il devrait quitter son service complètement libre : return this.httpClient.get<type>(...) . et ensuite avoir catch... quelque part en dehors du service où il le consomme réellement, car c'est là qu'il construira le flux d'observables et qu'il pourra le gérer au mieux.

0 votes

Return Observable.of(e) ; a fonctionné pour moi De cette façon, je peux reporter le traitement de l'exception à l'appelant.

1 votes

Je suis d'accord, peut-être qu'une solution optimale serait d'avoir les Promise<Object> (l'appelant de la méthode DataService ) pour traiter 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.

120voto

MegaCasper Points 444

Permettez-moi de mettre à jour le acdcjunior La réponse de l'auteur concernant l'utilisation de HttpInterceptor avec les dernières fonctionnalités de 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;
      })
    );
  }
}

17 votes

La réponse de acdcjunior est inutilisable à ce jour.

50voto

Jota.Toledo Points 11554

Avec l'arrivée de la HTTPClient API, non seulement l Http a été remplacée, mais une nouvelle a été ajoutée, la HttpInterceptor API.

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

Donc en supposant que vous voulez ajouter un comportement de traitement des erreurs par défaut en ajoutant .catch() à toutes vos méthodes http.get/post/etc possibles est ridiculement difficile à maintenir.

Cela pourrait être fait de la manière suivante, par exemple en utilisant un fichier 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 le PO : 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);
    }
}

Retour à Promises de vos méthodes de service au lieu de Observables est une autre mauvaise décision.

Et un conseil supplémentaire : si vous utilisez TYPE script, puis commencer à utiliser la partie type de celui-ci. Vous perdez l'un des plus grands avantages du langage : connaître le type de la valeur que vous traitez.

Si vous voulez un bon exemple, à mon avis, d'un service angulaire, jetez un coup d'œil à ce qui suit dans le gist suivant .

0 votes

Les commentaires ne sont pas destinés à une discussion approfondie ; cette conversation a été déplacé vers le chat .

0 votes

Je suppose que cela devrait être this.http.get() etc. et non this.get() etc. dans DataService ?

0 votes

La réponse choisie semble maintenant plus complète.

19voto

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

.pipe(catchError(this.errorHandler))

Code ci-dessous :

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 Angular pour Http

2 votes

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>>".

2voto

dee zg Points 4060

Vous voulez probablement avoir quelque chose comme ça :

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

Cela dépend fortement de l'utilisation que vous faites de votre service, mais c'est le cas de base.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X