web-dev-qa-db-fra.com

Correction des 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), je reçois un message de console désagréable: ERROR Error: Uncaught (promis): [objet Object] de core.es5.js = Comment est-ce que je le gère dans mon cas?

76
LastTribunal

Vous avez quelques options, selon vos besoins. Si vous souhaitez gérer les erreurs à la demande, ajoutez un catch à votre demande. Si vous souhaitez ajouter une solution globale, utilisez HttpInterceptor.

Ouvrez ici le logiciel de démonstration de travail pour les solutions ci-dessous.

tl; dr

Dans le cas le plus simple, vous aurez simplement besoin d'ajouter une .catch() ou une .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 de la méthode (locale): erreur de journal et réponse de secours retournée

Si vous ne devez gérer les erreurs qu’à un seul endroit, vous pouvez utiliser catch et renvoyer une valeur par défaut (ou une réponse vide) au lieu d’échouer complètement. Vous n'avez pas non plus besoin du .map pour lancer un casting, vous pouvez utiliser une fonction générique. Source: Angular.io - Obtention des détails sur l’erreur .

Ainsi, une méthode générique .get(), 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>();
          });
     }
}

Le traitement de l'erreur permettra à votre application de continuer même si le service à l'URL est en mauvais état.

Cette solution sur demande est utile 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 consiste à utiliser un intercepteur, comme décrit ci-dessous.

Exécutez le travail de la démo plunker ici .


Utilisation avancée: Interception de toutes les demandes ou réponses

Encore une fois, guide Angular.io montre:

Une caractéristique majeure de @angular/common/http est l'interception, la possibilité de déclarer des intercepteurs situés entre votre application et le backend. Lorsque votre application émet une demande, les intercepteurs la transforment avant de l'envoyer au serveur, et les intercepteurs peuvent transformer la réponse en retournant 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 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>>();
      });
  }
}

Fournir votre intercepteur: La simple déclaration de la HttpErrorInterceptor ci-dessus ne provoque pas son utilisation par votre application. Vous devez le câbler dans le module de votre application en le fournissant comme 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 un intercepteur d'erreur et une gestion des erreurs locales, il est probable que qu'aucune gestion d'erreur locale ne sera jamais déclenchée, car l'erreur sera toujours traitée par l'intercepteur avant il atteint la gestion d'erreur locale.

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

156
acdcjunior

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

Autant que je sache, l’un de ses objectifs est d’ajouter un comportement par défaut à toutes les demandes sortantes HTTP et à toutes les réponses entrantes.

Donc, en supposant que vous vouliez ajouter un comportement par défaut en matière de gestion des erreurs , 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, à 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 ne constitue 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);
    }
}

Renvoyer 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 un script TYPE, commencez à en utiliser la partie type. Vous perdez l'un des plus gros avantages de la langue: connaître le type de valeur que vous utilisez.

Si vous voulez, à mon avis, un bon exemple de service angular, jetez un oeil à à la Gist suivante .

45
Jota.Toledo

Permettez-moi de mettre à jour la réponse de acdcjunior à propos de 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;
      })
    );
  }
}
31
MegaCasper

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

3

Vous voulez probablement avoir quelque chose comme ça:

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

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

2
dee zg

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 angulaire

 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!');
    }
  );
2
Tomer

Après la réponse de @acdcjunior, voici comment je l'ai implémenté

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

Service de traitement des erreurs angulaire 8 HttpClient Exemple

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

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

    }
1
Code Spy

Si vous ne parvenez pas à détecter les erreurs avec l’une des solutions fournies ici, il se peut que le serveur ne traite pas les demandes CORS.

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

Recherchez des avertissements dans votre console incluant CORB ou Cross-Origin Read Blocking.

En outre, la syntaxe a été modifiée pour traiter les erreurs (comme décrit dans toutes les autres réponses). Vous utilisez maintenant des opérateurs pouvant être conduits, comme ceci:

this.service.requestsMyInfo(payload).pipe(
    catcheError(err => {
        // handle the error here.
    })
);
0
Kevin Beal
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
sun sreng

En utilisant Interceptor, vous pouvez attraper 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 .. étant donné un exemple simple pour cela.

0
Prashant M Bhavsar