Fehler beim Abfangen in Angular HttpClient

114

Ich habe einen Datendienst, der so aussieht:

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

Wenn ich einen HTTP-Fehler erhalte (z. B. 404), erhalte ich eine unangenehme Konsolenmeldung: FEHLER Fehler: Nicht erfasst (im Versprechen): [Objekt Objekt] von core.es5.js Wie gehe ich in meinem Fall damit um?

LastTribunal
quelle

Antworten:

230

Sie haben je nach Bedarf einige Optionen. Wenn Sie Fehler pro Anfrage behandeln möchten, fügen Sie catchIhrer Anfrage ein hinzu. Wenn Sie eine globale Lösung hinzufügen möchten, verwenden Sie HttpInterceptor.

Öffnen Sie hier den funktionierenden Demo-Plunker für die folgenden Lösungen.

tl; dr

Im einfachsten Fall müssen Sie nur ein .catch()oder ein hinzufügen .subscribe(), wie:

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

Aber es gibt noch mehr Details dazu, siehe unten.


Methode (lokal) Lösung: Fehler protokollieren und Fallback-Antwort zurückgeben

Wenn Sie Fehler nur an einer Stelle behandeln müssen, können Sie catcheinen Standardwert (oder eine leere Antwort) verwenden und zurückgeben, anstatt vollständig fehlzuschlagen. Sie brauchen auch nicht .mapnur das Casting, sondern können eine generische Funktion verwenden. Quelle: Angular.io - Abrufen von Fehlerdetails .

Eine generische .get()Methode wäre also wie folgt:

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

Durch die Behandlung des Fehlers kann die App auch dann fortgesetzt werden, wenn sich der Dienst unter der URL in einem schlechten Zustand befindet.

Diese Lösung pro Anforderung eignet sich vor allem dann, wenn Sie für jede Methode eine bestimmte Standardantwort zurückgeben möchten. Wenn Sie sich jedoch nur für die Fehleranzeige interessieren (oder eine globale Standardantwort haben), ist die bessere Lösung die Verwendung eines Interceptors, wie unten beschrieben.

Führen Sie hier den funktionierenden Demo-Plunker aus .


Erweiterte Verwendung: Abfangen aller Anforderungen oder Antworten

Der Angular.io-Leitfaden zeigt einmal mehr:

Ein Hauptmerkmal von @angular/common/httpist das Abfangen, die Fähigkeit, Abfangjäger zu deklarieren, die zwischen Ihrer Anwendung und dem Backend liegen. Wenn Ihre Anwendung eine Anforderung stellt, transformieren Interceptors diese, bevor sie an den Server gesendet werden, und die Interceptors können die Antwort auf dem Rückweg transformieren, bevor Ihre Anwendung sie sieht. Dies ist nützlich für alles von der Authentifizierung bis zur Protokollierung.

Was natürlich verwendet werden kann, um Fehler auf sehr einfache Weise zu behandeln ( Demo-Plunker hier ):

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

Bereitstellung Ihres Abfangjägers: Wenn Sie nur das HttpErrorInterceptoroben Gesagte deklarieren, wird es von Ihrer App nicht verwendet. Sie müssen es in Ihrem App-Modul verkabeln, indem Sie es wie folgt als Interceptor bereitstellen:

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

Hinweis: Wenn Sie sowohl einen Fehlerabfangjäger als auch eine lokale Fehlerbehandlung haben, wird natürlich wahrscheinlich nie eine lokale Fehlerbehandlung ausgelöst, da der Fehler immer vom Abfangjäger behandelt wird, bevor er die lokale Fehlerbehandlung erreicht.

Führen Sie hier den funktionierenden Demo-Plunker aus .

acdcjunior
quelle
2
Nun, wenn er Lust haben will, würde er seinen Dienst völlig frei lassen : return this.httpClient.get<type>(...). und dann catch...irgendwo außerhalb des Dienstes haben, wo er es tatsächlich konsumiert, weil er dort Observables Flow aufbauen wird und am besten damit umgehen kann.
dee zg
1
Ich bin damit einverstanden, vielleicht wäre eine optimale Lösung, den Promise<Object>Client (den Aufrufer der DataServiceMethoden) zu haben, um den Fehler zu behandeln. Beispiel : this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. Wählen Sie aus, was für Sie und die Benutzer Ihres Dienstes klarer ist.
acdcjunior
1
Dies wird nicht kompiliert: return Observable.of({my: "default value..."}); Es wird der Fehler "| ... 'kann nicht dem Typ' HttpEvent <any> 'zugewiesen werden."
Yakov Fain
1
@YakovFain Wenn Sie einen Standardwert im Interceptor möchten, muss dieser ein Wert sein HttpEvent, z HttpResponse. So könnten Sie zum Beispiel verwenden : return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));. Ich habe die Antwort aktualisiert, um diesen Punkt klar zu machen. Außerdem habe ich einen funktionierenden Demo-Plunker erstellt, um zu zeigen, dass alles funktioniert: plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview
acdcjunior
1
@acdcjunior, du bist ein Geschenk, das immer weiter gibt :)
LastTribunal
67

Lassen Sie mich das bitte aktualisieren acdcjunior ‚s Antwort über die Verwendung von HttpInterceptor mit den neuesten RxJs Funktionen (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
quelle
11
Dies muss mehr positiv bewertet werden. Die Antwort von acdcjunior ist bis heute unbrauchbar
Paul Kruger
48

Mit dem Eintreffen der HTTPClientAPI wurde nicht nur die HttpAPI ersetzt, sondern auch eine neue hinzugefügt, die HttpInterceptorAPI.

AFAIK hat sich zum Ziel gesetzt, allen ausgehenden HTTP-Anforderungen und eingehenden Antworten ein Standardverhalten hinzuzufügen.

So assumming , dass Sie einen hinzufügen möchten Standardfehlerbehandlung Verhalten , das Hinzufügen , .catch()um alle Ihre möglichen http.get / Post / etc Methoden ist lächerlich schwer zu pflegen.

Dies könnte folgendermaßen als Beispiel mit a erfolgen 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 {}

Einige zusätzliche Informationen für OP: Das Aufrufen von http.get / post / etc ohne einen starken Typ ist keine optimale Verwendung der API. Ihr Service sollte folgendermaßen aussehen:

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

Die Rückkehr Promisesvon Ihren Servicemethoden anstelle von Observablesist eine weitere schlechte Entscheidung.

Und noch ein zusätzlicher Ratschlag: Wenn Sie ein TYPE- Skript verwenden, beginnen Sie mit der Verwendung des Typs. Sie verlieren einen der größten Vorteile der Sprache: die Art des Wertes zu kennen, mit dem Sie es zu tun haben.

Wenn Sie meiner Meinung nach ein gutes Beispiel für einen eckigen Service wünschen, schauen Sie sich das folgende Wesentliche an .

Jota.Toledo
quelle
Kommentare sind nicht für eine ausführliche Diskussion gedacht. Dieses Gespräch wurde in den Chat verschoben .
Täuschung
Ich nehme an das sollte this.http.get()etc. sein und nicht this.get()etc. in DataService?
Anzeigename
Die ausgewählte Antwort scheint nun vollständiger zu sein.
Chris Haines
9

Bei Angular 6+ funktioniert .catch nicht direkt mit Observable. Du musst benutzen

.pipe(catchError(this.errorHandler))

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

Weitere Informationen finden Sie im Angular Guide for Http

Udith Indrakantha
quelle
1
Dies ist die einzige Antwort, die für mich funktioniert hat. Die anderen geben den Fehler aus: "Der Typ 'Observable <unbekannt>' kann nicht dem Typ 'Observable <HttpEvent <any>> zugewiesen werden".
König Arthur der Dritte
8

Ziemlich einfach (im Vergleich zu der vorherigen API).

Quelle aus (kopieren und einfügen) dem offiziellen Angular-Leitfaden

 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
quelle
5

Angular 8 Httpclient Fehlerbehandlung Service - Beispiel

Geben Sie hier die Bildbeschreibung ein

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

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

    }
Code Spy
quelle
2

Sie möchten wahrscheinlich so etwas haben:

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

Es hängt auch stark davon ab, wie Sie Ihren Service nutzen, aber dies ist der Grundfall.

dee zg
quelle
1

Nach der Antwort von @acdcjunior habe ich es so implementiert

Bedienung:

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

Anrufer:

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

Wenn Sie feststellen, dass Sie mit keiner der hier bereitgestellten Lösungen Fehler abfangen können, verarbeitet der Server möglicherweise keine CORS-Anforderungen.

In diesem Fall kann Javascript, geschweige denn Angular, auf die Fehlerinformationen zugreifen.

Suchen Sie in Ihrer Konsole nach Warnungen, die CORBoder enthaltenCross-Origin Read Blocking .

Außerdem hat sich die Syntax für die Behandlung von Fehlern geändert (wie in jeder anderen Antwort beschrieben). Sie verwenden jetzt Pipe-fähige Operatoren wie folgt:

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

Mit Interceptor können Sie Fehler abfangen. Unten ist 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");
        }
      }
    });
  }
}

Sie können diesen Blog bevorzugen .. gegebenes einfaches Beispiel dafür.

Prashant M Bhavsar
quelle