Menangkap kesalahan di Angular HttpClient

114

Saya memiliki layanan data yang terlihat seperti ini:

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

Jika saya mendapatkan kesalahan HTTP (yaitu 404), saya mendapatkan pesan konsol yang tidak menyenangkan: ERROR Error: Uncaught (in promise): [object Object] dari core.es5.js Bagaimana cara menanganinya dalam kasus saya?

LastTribunal
sumber

Jawaban:

231

Anda memiliki beberapa pilihan, tergantung pada kebutuhan Anda. Jika Anda ingin menangani kesalahan berdasarkan permintaan, tambahkan a catchke permintaan Anda. Jika Anda ingin menambahkan solusi global, gunakan HttpInterceptor.

Buka di sini plunker demo yang berfungsi untuk solusi di bawah ini.

tl; dr

Dalam kasus yang paling sederhana, Anda hanya perlu menambahkan a .catch()atau .subscribe(), seperti:

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

Tetapi ada lebih banyak detail untuk ini, lihat di bawah.


Solusi metode (lokal): kesalahan log dan respons fallback kembali

Jika Anda perlu menangani kesalahan hanya di satu tempat, Anda dapat menggunakan catchdan mengembalikan nilai default (atau respons kosong) alih-alih gagal sepenuhnya. Anda juga tidak perlu .maphanya untuk mentransmisikan, Anda dapat menggunakan fungsi generik. Sumber: Angular.io - Mendapatkan Detail Kesalahan .

Jadi, .get()metode umum , akan seperti:

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

Menangani kesalahan akan memungkinkan aplikasi Anda untuk melanjutkan bahkan ketika layanan di URL dalam kondisi buruk.

Solusi per permintaan ini baik terutama saat Anda ingin mengembalikan respons default tertentu ke setiap metode. Tetapi jika Anda hanya peduli dengan tampilan kesalahan (atau memiliki respons default global), solusi yang lebih baik adalah menggunakan pencegat, seperti yang dijelaskan di bawah ini.

Jalankan plunker demo yang berfungsi di sini .


Penggunaan lanjutan: Mencegah semua permintaan atau tanggapan

Sekali lagi, panduan Angular.io menunjukkan:

Fitur utama dari @angular/common/httpadalah intersepsi, kemampuan untuk mendeklarasikan interseptor yang berada di antara aplikasi Anda dan backend. Saat aplikasi Anda membuat permintaan, interseptor mengubahnya sebelum mengirimnya ke server, dan interseptor bisa mengubah respons dalam perjalanan kembali sebelum aplikasi Anda melihatnya. Ini berguna untuk segala hal mulai dari otentikasi hingga logging.

Yang, tentu saja, dapat digunakan untuk menangani kesalahan dengan cara yang sangat sederhana ( demo plunker di sini ):

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

Menyediakan interseptor Anda: Cukup mendeklarasikan hal di HttpErrorInterceptoratas tidak akan menyebabkan aplikasi Anda menggunakannya. Anda perlu menyambungkannya dalam modul aplikasi Anda dengan menyediakannya sebagai pencegat, sebagai berikut:

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

Catatan: Jika Anda memiliki kedua pencegat kesalahan dan beberapa penanganan kesalahan lokal, secara alami, ada kemungkinan bahwa ada kesalahan penanganan lokal akan pernah dipicu, karena kesalahan akan selalu ditangani oleh pencegat sebelum mencapai penanganan kesalahan lokal.

Jalankan plunker demo yang berfungsi di sini .

acdcjunior.dll
sumber
2
baik, jika ia ingin sepenuhnya suka dia akan meninggalkan pengabdiannya sepenuhnya jelas: return this.httpClient.get<type>(...). dan kemudian memiliki catch...suatu tempat di luar layanan di mana dia benar-benar mengonsumsinya karena di situlah dia akan membangun aliran yang dapat diamati dan dapat menanganinya dengan baik.
dee zg
1
Saya setuju, mungkin solusi optimal akan memiliki Promise<Object>klien (pemanggil DataServicemetode) untuk menangani kesalahan. Contoh: this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. Pilih apa saja yang lebih jelas bagi Anda dan pengguna layanan Anda.
acdcjunior
1
Ini tidak dapat dikompilasi: return Observable.of({my: "default value..."}); Ini memberikan kesalahan "| ... 'tidak dapat ditetapkan untuk mengetik' HttpEvent <any> '."
Yakov Fain
1
@YakovFain Jika Anda menginginkan nilai default di interseptor, itu harus berupa HttpEvent, seperti a HttpResponse. Jadi, misalnya, Anda bisa menggunakan: return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));. Saya telah memperbarui jawaban untuk memperjelas hal ini. Juga, saya membuat plunker demo yang berfungsi untuk menunjukkan semuanya berfungsi: plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview
acdcjunior
1
@acdcjunior, Anda adalah hadiah yang terus memberi :)
LastTribunal
67

Mari saya perbarui acdcjunior jawaban 's tentang menggunakan HttpInterceptor dengan fitur RxJs terbaru (ayat 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
sumber
11
Ini perlu mendapat lebih banyak suara positif. Jawaban acdcjunior tidak dapat digunakan mulai hari ini
Paul Kruger
48

Dengan hadirnya HTTPClientAPI, API tidak hanya Httpdiganti, tetapi yang baru telah ditambahkan, HttpInterceptorAPI.

AFAIK salah satu tujuannya adalah menambahkan perilaku default ke semua permintaan keluar HTTP dan tanggapan masuk.

Jadi mengasumsikan bahwa Anda ingin menambahkan perilaku penanganan kesalahan default , menambahkan .catch()ke semua kemungkinan metode http.get / post / etc Anda sangat sulit untuk dipertahankan.

Ini dapat dilakukan dengan cara berikut sebagai contoh menggunakan a 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 {}

Beberapa info tambahan untuk OP: Memanggil http.get / post / etc tanpa tipe yang kuat bukanlah penggunaan API yang optimal. Layanan Anda akan terlihat seperti ini:

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

Kembali Promisesdari metode layanan Anda, bukan Observableskeputusan buruk lainnya.

Dan saran tambahan: jika Anda menggunakan skrip TYPE , mulailah menggunakan bagian type. Anda kehilangan salah satu keuntungan terbesar dari bahasa tersebut: mengetahui jenis nilai yang Anda hadapi.

Jika Anda menginginkan, menurut pendapat saya, contoh yang baik dari layanan sudut, lihat inti berikut .

Jota.Toledo
sumber
Komentar tidak untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
menipu
Saya berasumsi ini harus this.http.get()dll dan tidak this.get()dll di DataService?
tampilan
Jawaban yang dipilih tampaknya sekarang lebih lengkap.
Chris Haines
9

Cukup mudah (dibandingkan dengan cara melakukannya dengan API sebelumnya).

Sumber dari (salin dan tempel) panduan resmi 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
sumber
9

Untuk Angular 6+, .catch tidak bekerja secara langsung dengan Observable. Anda harus menggunakan

.pipe(catchError(this.errorHandler))

Kode di bawah ini:

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

Untuk lebih jelasnya, rujuk ke Angular Guide for Http

Udith Indrakantha
sumber
1
Ini adalah satu-satunya jawaban yang berhasil untuk saya. Yang lain memberikan kesalahan: "Type 'Observable <unknown>' is not assignable to type 'Observable <HttpEvent <any>>".
Raja Arthur Ketiga
5

Angular 8 Contoh Layanan Penanganan Error HttpClient

masukkan deskripsi gambar di sini

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

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

    }
Kode Spy
sumber
2

Anda mungkin ingin memiliki sesuatu seperti ini:

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

Ini juga sangat tergantung bagaimana Anda menggunakan layanan Anda, tetapi ini adalah kasus dasar.

dee zg
sumber
1

Mengikuti jawaban @acdcjunior, beginilah cara saya menerapkannya

layanan:

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

penelepon:

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

Jika Anda tidak dapat menemukan kesalahan dengan solusi apa pun yang disediakan di sini, mungkin server tidak menangani permintaan CORS.

Dalam acara tersebut, Javascript, apalagi Angular, dapat mengakses informasi kesalahan.

Cari peringatan di konsol Anda yang menyertakan CORBatau Cross-Origin Read Blocking.

Selain itu, sintaksis telah berubah untuk menangani kesalahan (seperti yang dijelaskan di setiap jawaban lainnya). Anda sekarang menggunakan operator yang dapat menggunakan pipa, seperti:

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

Dengan menggunakan Interceptor Anda dapat menangkap kesalahan. Di bawah ini adalah kode:

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

Anda dapat memilih blog ini .. diberikan contoh sederhana untuk itu.

Prashant M Bhavsar
sumber