การจับข้อผิดพลาดใน Angular HttpClient


117

ฉันมีบริการข้อมูลที่มีลักษณะดังนี้:

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

หากฉันได้รับข้อผิดพลาด HTTP (เช่น 404) ฉันได้รับข้อความคอนโซลที่น่ารังเกียจ: ข้อผิดพลาด ERROR: Uncaught (ตามสัญญา): [object Object]จากcore.es5.js ฉันจะจัดการกับมันอย่างไรในกรณีของฉัน?

คำตอบ:


234

คุณมีทางเลือกบางอย่างขึ้นอยู่กับความต้องการของคุณ หากคุณต้องการจัดการข้อผิดพลาดตามคำขอให้เพิ่มcatchคำขอของคุณ HttpInterceptorหากคุณต้องการที่จะเพิ่มการแก้ปัญหาที่ทั่วโลกใช้

เปิดที่นี่เครื่องมือสาธิตการใช้งานสำหรับโซลูชันด้านล่าง

tl; dr

ในกรณีที่ง่ายที่สุดคุณจะต้องเพิ่ม a .catch()หรือ a .subscribe()เช่น:

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

แต่มีรายละเอียดเพิ่มเติมโปรดดูด้านล่าง


วิธีการแก้ปัญหา (ภายใน): ข้อผิดพลาดในการบันทึกและส่งคืนการตอบกลับทางเลือก

หากคุณต้องการจัดการข้อผิดพลาดในที่เดียวคุณสามารถใช้catchและส่งคืนค่าเริ่มต้น (หรือการตอบกลับที่ว่างเปล่า) แทนที่จะล้มเหลวทั้งหมด คุณไม่จำเป็นต้องใช้.mapเพียงแค่ในการแคสต์คุณสามารถใช้ฟังก์ชันทั่วไปได้ ที่มา: Angular.io - รับข้อผิดพลาดรายละเอียด

ดังนั้น.get()วิธีการทั่วไปจะเป็นดังนี้:

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

การจัดการข้อผิดพลาดจะช่วยให้แอปของคุณดำเนินการต่อได้แม้ว่าบริการที่ URL จะอยู่ในสภาพไม่ดีก็ตาม

โซลูชันต่อคำขอนี้เป็นวิธีที่ดีส่วนใหญ่เมื่อคุณต้องการส่งคืนการตอบกลับเริ่มต้นเฉพาะสำหรับแต่ละวิธี แต่ถ้าคุณสนใจเฉพาะการแสดงข้อผิดพลาด (หรือมีการตอบสนองเริ่มต้นทั่วโลก) วิธีแก้ปัญหาที่ดีกว่าคือใช้ตัวสกัดกั้นตามที่อธิบายไว้ด้านล่าง

เรียกใช้plunker สาธิตการทำงานที่นี่


การใช้งานขั้นสูง: สกัดกั้นคำขอหรือการตอบกลับทั้งหมด

อีกครั้งคู่มือ Angular.ioแสดง:

คุณสมบัติหลักของการ@angular/common/httpสกัดกั้นคือความสามารถในการประกาศตัวสกัดกั้นซึ่งอยู่ระหว่างแอปพลิเคชันของคุณและแบ็กเอนด์ เมื่อแอปพลิเคชันของคุณส่งคำขอตัวสกัดกั้นจะเปลี่ยนรูปแบบก่อนที่จะส่งไปยังเซิร์ฟเวอร์และตัวสกัดสามารถเปลี่ยนการตอบสนองกลับก่อนที่แอปพลิเคชันของคุณจะเห็น สิ่งนี้มีประโยชน์สำหรับทุกอย่างตั้งแต่การตรวจสอบสิทธิ์ไปจนถึงการบันทึก

ซึ่งแน่นอนว่าสามารถใช้เพื่อจัดการข้อผิดพลาดได้ด้วยวิธีง่ายๆ ( 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>>();
      });
  }
}

การจัดหาเครื่องสกัดกั้นของคุณ:เพียงแค่ประกาศHttpErrorInterceptorข้างต้นไม่ได้ทำให้แอปของคุณใช้งานได้ คุณต้องวางสายในโมดูลแอปของคุณโดยจัดให้เป็นตัวสกัดกั้นดังต่อไปนี้:

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

หมายเหตุ:หากคุณมีทั้งตัวดักจับข้อผิดพลาดและการจัดการข้อผิดพลาดในเครื่องโดยปกติแล้วจะไม่มีการทริกเกอร์การจัดการข้อผิดพลาดในพื้นที่เนื่องจากข้อผิดพลาดจะได้รับการจัดการโดยผู้สกัดกั้นก่อนที่จะถึงการจัดการข้อผิดพลาดในเครื่อง

เรียกใช้plunker สาธิตการทำงานที่นี่


2
return this.httpClient.get<type>(...)ดีถ้าเขาต้องการที่จะได้รับอย่างเต็มที่แฟนซีเขาจะออกจากบริการของเขาชัดเจนอย่างเต็มที่: จากนั้นก็มีสถานบริการcatch...ที่ไหนสักแห่งที่เขาใช้มันจริงๆเพราะนั่นคือจุดที่เขาจะสร้างกระแสการสังเกตและสามารถจัดการได้ดีที่สุด
dee zg

1
ฉันเห็นด้วยบางทีทางออกที่ดีที่สุดคือให้Promise<Object>ลูกค้า (ผู้เรียกใช้DataServiceวิธีการ) จัดการข้อผิดพลาด ตัวอย่าง: this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. เลือกสิ่งที่ชัดเจนสำหรับคุณและผู้ใช้บริการของคุณ
acdcjunior

1
สิ่งนี้ไม่ได้รวบรวม: return Observable.of({my: "default value..."}); มันทำให้เกิดข้อผิดพลาด "| ... " ไม่สามารถกำหนดให้พิมพ์ "HttpEvent <any>" "
Yakov Fain

1
@YakovFain หากคุณต้องการค่าเริ่มต้นในตัวสกัดกั้นจะต้องเป็น a HttpEventเช่นHttpResponse. return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));ดังนั้นสำหรับตัวอย่างเช่นคุณสามารถใช้: ฉันได้อัปเดตคำตอบเพื่อให้ประเด็นนี้ชัดเจน นอกจากนี้ฉันได้สร้างเครื่องมือสาธิตที่ใช้งานได้เพื่อแสดงทุกอย่างที่ใช้งานได้: plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview
acdcjunior

1
@acdcjunior คุณคือของขวัญที่มอบให้ :)
LastTribunal

70

ผมขอโปรดปรับปรุงacdcjuniorคำตอบ 's เกี่ยวกับการใช้HttpInterceptorด้วยคุณสมบัติ 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;
      })
    );
  }
}

12
สิ่งนี้ต้องได้รับการโหวตมากขึ้น คำตอบของ acdcjunior ใช้ไม่ได้ ณ วันนี้
Paul Kruger

48

เมื่อมาถึงของHTTPClientAPI ไม่เพียง แต่HttpAPI ถูกแทนที่ แต่ยังมีการเพิ่มHttpInterceptorAPI ใหม่อีกด้วย

AFAIK หนึ่งในเป้าหมายคือการเพิ่มพฤติกรรมเริ่มต้นให้กับคำขอส่งออก HTTP และการตอบกลับที่เข้ามาทั้งหมด

ดังนั้น assumming ที่คุณต้องการเพิ่มพฤติกรรมการจัดการข้อผิดพลาดเริ่มต้นเพิ่ม.catch()ทั้งหมดไปได้ http.get / โพสต์ / วิธีการอื่น ๆ เป็นเรื่องยากที่จะรักษาขัน

สามารถทำได้ด้วยวิธีต่อไปนี้ดังตัวอย่างโดยใช้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: การเรียก http.get / post / etc โดยไม่มีประเภทที่รัดกุมไม่ใช่การใช้ API ที่เหมาะสมที่สุด บริการของคุณควรมีลักษณะดังนี้:

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

การกลับมาPromisesจากวิธีการให้บริการของคุณObservablesเป็นการตัดสินใจที่ไม่ดีอีกอย่างหนึ่ง

และคำแนะนำเพิ่มเติม: หากคุณใช้สคริปต์TYPEให้เริ่มใช้ส่วนประเภทของมัน คุณสูญเสียข้อได้เปรียบที่สำคัญที่สุดอย่างหนึ่งของภาษานั่นคือการรู้ประเภทของคุณค่าที่คุณกำลังเผชิญอยู่

ถ้าคุณต้องการในความคิดของฉันตัวอย่างที่ดีของการบริการเชิงมุมจะดูที่สรุปสาระสำคัญดังต่อไปนี้


ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
หลอกลวง

ฉันคิดว่าสิ่งนี้ควรเป็นthis.http.get()ฯลฯ และไม่ใช่this.get()ฯลฯ ในDataService?
DisplayName

คำตอบที่เลือกดูเหมือนจะสมบูรณ์มากขึ้น
Chris Haines

11

สำหรับ Angular 6+ .catch ใช้ไม่ได้โดยตรงกับ Observable คุณต้องใช้

.pipe(catchError(this.errorHandler))

ด้านล่างรหัส:

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

สำหรับรายละเอียดเพิ่มเติมโปรดดูAngular Guide สำหรับ Http


1
นี่เป็นคำตอบเดียวที่เหมาะกับฉัน ข้อผิดพลาดอื่น ๆ ให้ข้อผิดพลาดของ: "Type 'Observable <unknown>' ไม่สามารถกำหนดให้พิมพ์ 'Observable <HttpEvent <any>>"
King Arthur the Third

9

ค่อนข้างตรงไปตรงมา (เมื่อเทียบกับวิธีที่ทำกับ API ก่อนหน้านี้)

แหล่งที่มาจาก (คัดลอกและวาง) คู่มืออย่างเป็นทางการ 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!');
    }
  );

5

Angular 8 HttpClient Error Handling Service ตัวอย่าง

ป้อนคำอธิบายภาพที่นี่

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

คุณอาจต้องการมีสิ่งนี้:

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

ขึ้นอยู่กับว่าคุณใช้บริการของคุณอย่างไร แต่นี่เป็นกรณีพื้นฐาน


1

ตามคำตอบของ @acdcjunior นี่คือวิธีที่ฉันนำไปใช้

บริการ:

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

ผู้โทร:

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

หากคุณพบว่าตัวเองไม่สามารถตรวจจับข้อผิดพลาดกับแนวทางแก้ไขใด ๆ ที่ให้ไว้ที่นี่อาจเป็นเพราะเซิร์ฟเวอร์ไม่ได้จัดการคำขอ CORS

ในกรณีนั้น Javascript ซึ่งมี Angular น้อยกว่ามากสามารถเข้าถึงข้อมูลข้อผิดพลาดได้

มองหาคำเตือนในคอนโซลของคุณที่มีหรือCORBCross-Origin Read Blocking

นอกจากนี้ไวยากรณ์ยังเปลี่ยนไปสำหรับการจัดการข้อผิดพลาด (ตามที่อธิบายไว้ในคำตอบอื่น ๆ ทั้งหมด) ตอนนี้คุณใช้ตัวดำเนินการที่สามารถไปป์ไลน์ได้ดังนี้:

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

0

โดยใช้ Interceptor คุณสามารถตรวจจับข้อผิดพลาด ด้านล่างนี้คือรหัส:

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

คุณสามารถชอบบล็อกนี้ .. ยกตัวอย่างง่ายๆสำหรับมัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.