Angular HttpClient'te hataları yakalama


114

Şuna benzeyen bir veri hizmetim var:

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

Ben bir HTTP hatası (yani 404) alırsam, kötü konsol mesajı alıyorum: HATA hatası: (sözden) yakalanmamış: [object Object] dan core.es5.js benim durumda idare nasıl?

Yanıtlar:


231

İhtiyaçlarınıza bağlı olarak bazı seçenekleriniz var. Hataları istek bazında ele almak istiyorsanız, isteğinize bir ekleyin catch. Global bir çözüm eklemek istiyorsanız, kullanın HttpInterceptor.

Aşağıdaki çözümler için burada çalışan demo plunker'ı açın .

tl; dr

En basit durumda, sadece a .catch()veya a eklemeniz gerekir .subscribe(), örneğin:

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

Ancak bununla ilgili daha fazla ayrıntı var, aşağıya bakın.


Yöntem (yerel) çözüm: günlük hatası ve geri dönüş yanıtı

Hataları yalnızca bir yerde işlemeniz gerekiyorsa, catchtamamen başarısız olmak yerine varsayılan bir değer (veya boş yanıt) kullanabilir ve döndürebilirsiniz. Ayrıca, .mapsadece yayınlamak için ihtiyacınız yok , genel bir işlev kullanabilirsiniz. Kaynak: Angular.io - Hata Ayrıntılarını Alma .

Dolayısıyla, genel bir .get()yöntem şöyle olacaktır:

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

Hatayı ele almak, URL'deki hizmet kötü durumda olsa bile uygulamanıza devam etmenize olanak tanır.

Bu istek başına çözüm, çoğunlukla her yönteme belirli bir varsayılan yanıt döndürmek istediğinizde iyidir. Ancak, yalnızca hata görüntülemeyi önemsiyorsanız (veya genel bir varsayılan yanıtınız varsa), daha iyi çözüm aşağıda açıklandığı gibi bir durdurucu kullanmaktır.

Çalışan demo plunker'ı buradan çalıştırın .


Gelişmiş kullanım: Tüm istekleri veya yanıtları yakalamak

Angular.io kılavuzu bir kez daha şunu gösterir:

Başlıca bir özelliği, @angular/common/httpuygulamanız ile arka uç arasında bulunan engelleyicileri bildirme yeteneği olan müdahaledir. Uygulamanız bir talepte bulunduğunda, müdahaleciler bunu sunucuya göndermeden önce dönüştürür ve önleyiciler, uygulamanız görmeden önce yanıtı geri dönüş yolunda dönüştürebilir. Bu, kimlik doğrulamadan günlüğe kaydetmeye kadar her şey için yararlıdır.

Elbette ki, hataları çok basit bir şekilde ele almak için kullanılabilir ( burada demo plunker ):

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

Önleyicinizi sağlama:HttpErrorInterceptor Yukarıdakileri beyan etmeniz, uygulamanızın onu kullanmasına neden olmaz. Bunu , aşağıdaki gibi bir müdahale aracı olarak sağlayarak uygulama modülünüzde bağlamanız gerekir:

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

Not: Eğer varsa hem bir hata önleme ve bazı yerel hata işleme doğal olarak, bu hata hep dinleyici ile ele alınacaktır beri hiçbir yerel hata işleme hiç tetiklenebilir olasıdır önce yerel hata işleme ulaşır.

Çalışan demo plunker'ı buradan çalıştırın .


2
O tamamen olmak istiyorsa iyi, o tamamen berrak onun hizmetini bırakacaktı fantezi: return this.httpClient.get<type>(...). ve sonra catch...hizmetin dışında gerçekten tükettiği bir yere sahip olun, çünkü burada gözlemlenebilir akış oluşturacağı ve en iyi şekilde başa çıkabileceği yer.
dee zg

1
Kabul ediyorum, belki de en uygun çözüm Promise<Object>müşterinin ( DataServiceyöntemlerini arayan ) hatayı ele alması olabilir. Örnek: this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. Siz ve hizmetinizin kullanıcıları için daha açık olanı seçin.
acdcjunior

1
Bu return Observable.of({my: "default value..."}); derlemez : "| ...", 'HttpEvent <any>' türüne atanamaz. "
Yakov Fain

1
@YakovFain Önleyicide varsayılan bir değer istiyorsanız HttpEvent, a HttpResponse. Dolayısıyla, örneğin, şunu kullanabilirsiniz: return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));. Bu noktayı netleştirmek için cevabı güncelledim. Ayrıca, çalışan her şeyi göstermek için çalışan bir demo plunker oluşturdum: plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview
acdcjunior

1
@acdcjunior, vermeye devam eden bir hediyesiniz :)
LastTribunal

67

Lütfen acdcjunior'un HttpInterceptor'ı en son RxJs özellikleriyle (v.6) kullanma konusundaki yanıtını güncellememe izin verin .

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
Bunun daha fazla oy alması gerekiyor. acdcjunior'un cevabı bugün itibariyle kullanılamaz
Paul Kruger

48

HTTPClientAPI'nin gelişiyle, yalnızca HttpAPI değiştirilmekle kalmadı , aynı zamanda yeni bir HttpInterceptorAPI eklendi .

AFAIK'in hedeflerinden biri, tüm HTTP giden isteklerine ve gelen yanıtlara varsayılan davranış eklemektir.

Yani bir eklemek istediğiniz assumming varsayılan hata işleme davranışı ekleyerek .catch()vb / senin mümkün http.get / yazının tüm korumak için gülünç sert yöntemler edilir.

Bu, a kullanılarak örnek olarak aşağıdaki şekilde yapılabilir 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 {}

OP için bazı ekstra bilgiler: Güçlü bir tür olmadan http.get / post / etc'yi çağırmak, API'nin optimum kullanımı değildir. Hizmetiniz şöyle görünmelidir:

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

PromisesBunun yerine servis yöntemlerinden dönmek Observablesbaşka bir kötü karardır.

Ve ekstra bir tavsiye: TYPE komut dosyası kullanıyorsanız , o zaman yazım kısmını kullanmaya başlayın. Dilin en büyük avantajlarından birini kaybediyorsunuz: uğraştığınız değerin türünü bilmek.

Kanımca, açısal bir hizmetin iyi bir örneğini istiyorsanız , aşağıdaki özete bir göz atın .


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
ezik

Ben bu olmalı varsayalım this.http.get()vs değil this.get()vs. DataService?
ekran adı

Seçilen cevap artık daha eksiksiz görünüyor.
Chris Haines

9

Oldukça basit (önceki API ile nasıl yapıldığına kıyasla).

Angular resmi kılavuzdan kaynak (kopyalayıp yapıştırarak)

 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

Angular 6+ için .catch, Observable ile doğrudan çalışmaz. Kullanmak zorundasın

.pipe(catchError(this.errorHandler))

Kodun altında:

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

Daha fazla ayrıntı için, Http için Açısal Kılavuza bakın


1
Benim için işe yarayan tek cevap bu. Diğerleri şu hata verir: "'Observable <unknown>' yazın 'Observable <HttpEvent <any>>" tipine atanamaz.
Kral Arthur Üçüncü

5

Angular 8 HttpClient Hata İşleme Hizmet Örneği

görüntü açıklamasını buraya girin

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

Muhtemelen böyle bir şeye sahip olmak istersiniz:

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

Hizmetinizi nasıl kullanacağınıza da büyük ölçüde bağlıdır, ancak bu temel durumdur.


1

@Acdcjunior cevabının ardından, bunu böyle uyguladım

hizmet:

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

arayan:

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

Burada sunulan çözümlerden herhangi birinde hata yakalayamıyorsanız, bunun nedeni sunucunun CORS isteklerini işlememesi olabilir.

Bu durumda, Javascript, çok daha az Angular, hata bilgilerine erişebilir.

Konsolunuzda CORBveya içeren uyarıları arayın Cross-Origin Read Blocking.

Ayrıca, hataların işlenmesi için sözdizimi de değiştirildi (her diğer yanıtta açıklandığı gibi). Artık, borulabilir operatörler kullanıyorsunuz, örneğin:

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

0

Interceptor'ı kullanarak hata yakalayabilirsiniz. Kod aşağıdadır:

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

Basit bir örnek verilmiş bu blogu tercih edebilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.