Bắt lỗi trong Angular HttpClient


114

Tôi có một dịch vụ dữ liệu giống như sau:

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

Nếu tôi gặp lỗi HTTP (tức là 404), tôi nhận được một thông báo khó chịu trên bảng điều khiển: Lỗi LỖI: Chưa nghĩ (trong lời hứa): [đối tượng Đối tượng] từ core.es5.js Làm cách nào để xử lý nó trong trường hợp của tôi?

Câu trả lời:


231

Bạn có một số tùy chọn, tùy thuộc vào nhu cầu của bạn. Nếu bạn muốn xử lý lỗi trên cơ sở từng yêu cầu, hãy thêm một catchvào yêu cầu của bạn. Nếu bạn muốn thêm một giải pháp toàn cầu, hãy sử dụng HttpInterceptor.

Mở plunker demo hoạt động ở đây cho các giải pháp bên dưới.

tl; dr

Trong trường hợp đơn giản nhất, bạn chỉ cần thêm một .catch()hoặc một .subscribe(), như:

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

Nhưng có nhiều chi tiết hơn về điều này, xem bên dưới.


Giải pháp phương pháp (cục bộ): lỗi nhật ký và trả về phản hồi dự phòng

Nếu bạn chỉ cần xử lý lỗi ở một nơi, bạn có thể sử dụng catchvà trả về giá trị mặc định (hoặc phản hồi trống) thay vì thất bại hoàn toàn. Bạn cũng không cần .mapchỉ để ép kiểu, bạn có thể sử dụng một hàm chung. Nguồn: Angular.io - Lấy chi tiết lỗi .

Vì vậy, một .get()phương pháp chung , sẽ giống như:

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

Xử lý lỗi sẽ cho phép ứng dụng của bạn tiếp tục ngay cả khi dịch vụ tại URL ở trong tình trạng không tốt.

Giải pháp theo yêu cầu này chủ yếu tốt khi bạn muốn trả về một phản hồi mặc định cụ thể cho từng phương thức. Nhưng nếu bạn chỉ quan tâm đến việc hiển thị lỗi (hoặc có phản hồi mặc định chung), giải pháp tốt hơn là sử dụng bộ chặn, như được mô tả bên dưới.

Chạy plunker demo đang hoạt động tại đây .


Sử dụng nâng cao: Chặn tất cả các yêu cầu hoặc phản hồi

Một lần nữa, hướng dẫn Angular.io cho thấy:

Một tính năng chính của @angular/common/httplà đánh chặn, khả năng khai báo các trình đánh chặn nằm giữa ứng dụng của bạn và chương trình phụ trợ. Khi ứng dụng của bạn đưa ra một yêu cầu, các bộ chặn sẽ biến đổi nó trước khi gửi đến máy chủ và các bộ chặn có thể chuyển đổi phản hồi theo cách của nó trở lại trước khi ứng dụng của bạn nhìn thấy nó. Điều này hữu ích cho mọi thứ từ xác thực đến ghi nhật ký.

Tất nhiên, có thể được sử dụng để xử lý lỗi một cách rất đơn giản ( demo plunker tại đây ):

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

Cung cấp công cụ chặn của bạn: Chỉ cần khai báo những điều HttpErrorInterceptortrên không khiến ứng dụng của bạn sử dụng nó. Bạn cần kết nối nó trong mô-đun ứng dụng của mình bằng cách cung cấp nó như một thiết bị đánh chặn, như sau:

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

Lưu ý: Nếu bạn có cả bộ chặn lỗi và một số xử lý lỗi cục bộ, thì có khả năng sẽ không có xử lý lỗi cục bộ nào được kích hoạt, vì lỗi sẽ luôn được bộ đánh chặn xử lý trước khi nó đến bộ xử lý lỗi cục bộ.

Chạy plunker demo đang hoạt động tại đây .


2
tốt, nếu anh muốn trở thành hoàn toàn ưa thích ông sẽ rời khỏi dịch vụ của mình hoàn toàn rõ ràng: return this.httpClient.get<type>(...). và sau đó có catch...một nơi nào đó ngoài dịch vụ nơi anh ta thực sự sử dụng nó bởi vì đó là nơi anh ta sẽ xây dựng luồng có thể quan sát và có thể xử lý nó tốt nhất.
dee zg

1
Tôi đồng ý, có thể một giải pháp tối ưu là có Promise<Object>ứng dụng khách (người gọi các DataServicephương thức của ') để xử lý lỗi. Ví dụ: this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. Chọn bất kỳ điều gì rõ ràng hơn đối với bạn và người dùng dịch vụ của bạn.
acdcjunior

1
Điều này không biên dịch: return Observable.of({my: "default value..."}); Nó đưa ra lỗi "| ... 'không thể gán cho kiểu' HttpEvent <any> '."
Yakov Fain

1
@YakovFain Nếu bạn muốn một giá trị mặc định trong bộ chặn, nó phải là a HttpEvent, chẳng hạn như a HttpResponse. Vì vậy, ví dụ, bạn có thể sử dụng: return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));. Tôi đã cập nhật câu trả lời để làm rõ điểm này. Ngoài ra, tôi đã tạo một plunker demo đang hoạt động để hiển thị mọi thứ đang hoạt động: plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview
acdcjunior

1
@acdcjunior, bạn là một món quà không ngừng trao tặng :)
LastTribunal

67

Hãy để tôi vui lòng cập nhật các acdcjunior câu trả lời 's về việc sử dụng HttpInterceptor với các tính năng RxJs mới nhất (câu 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;
      })
    );
  }
}

11
Điều này cần được ủng hộ nhiều hơn. Câu trả lời của acdcjunior không sử dụng được kể từ hôm nay
Paul Kruger

48

Với sự xuất hiện của HTTPClientAPI, không chỉ HttpAPI được thay thế mà một API mới đã được thêm vào, HttpInterceptorAPI.

AFAIK một trong những mục tiêu của nó là thêm hành vi mặc định cho tất cả các yêu cầu gửi đi HTTP và phản hồi đến.

Vì vậy, giả sử rằng bạn muốn thêm một hành vi xử lý lỗi mặc định , việc thêm.catch() vào tất cả các phương thức http.get / post / etc có thể có của bạn là rất khó duy trì.

Điều này có thể được thực hiện theo cách sau đây như ví dụ bằng cách sử dụng 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 {}

Một số thông tin bổ sung cho OP: Gọi http.get / post / etc mà không có loại mạnh không phải là cách sử dụng API tối ưu. Dịch vụ của bạn sẽ trông như thế này:

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

Quay trở lại Promisestừ các phương pháp dịch vụ của bạn thay vì Observableslà một quyết định tồi.

Và một lời khuyên bổ sung: nếu bạn đang sử dụng tập lệnh TYPE , thì hãy bắt đầu sử dụng phần loại của nó. Bạn đánh mất một trong những lợi thế lớn nhất của ngôn ngữ: biết loại giá trị mà bạn đang xử lý.

Theo ý kiến ​​của tôi, nếu bạn muốn một ví dụ điển hình về dịch vụ góc cạnh, hãy xem ý chính sau đây .


Nhận xét không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
dối trá

Tôi cho rằng điều này phải là this.http.get()vv và không phải this.get()vv trong DataService?
hiển thị

Câu trả lời đã chọn hiện đã hoàn thiện hơn.
Chris Haines

9

Đối với Angular 6+, .catch không hoạt động trực tiếp với Observable. Bạn phải sử dụng

.pipe(catchError(this.errorHandler))

Mã bên dưới:

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

Để biết thêm chi tiết, hãy tham khảo Hướng dẫn Angular cho Http


1
Đây là câu trả lời duy nhất phù hợp với tôi. Những người khác đưa ra lỗi: "Không thể gán loại 'Quan sát được <không xác định>' thành loại 'Có thể quan sát <HttpEvent <any>>".
Vua Arthur Đệ Tam

8

Khá đơn giản (so với cách nó được thực hiện với API trước đó).

Nguồn từ (sao chép và dán) hướng dẫn chính thức của 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

Ví dụ về dịch vụ xử lý lỗi Angular 8 HttpClient

nhập mô tả hình ảnh ở đây

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

Bạn có thể muốn có một cái gì đó như thế này:

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

Nó cũng phụ thuộc rất nhiều vào cách bạn sử dụng dịch vụ của mình nhưng đây là trường hợp cơ bản.


1

Sau câu trả lời @acdcjunior, đây là cách tôi triển khai nó

dịch vụ:

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

người gọi:

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

Nếu bạn thấy mình không thể bắt lỗi với bất kỳ giải pháp nào được cung cấp ở đây, có thể máy chủ không xử lý các yêu cầu CORS.

Trong trường hợp đó, Javascript, ít Angular hơn nhiều, có thể truy cập thông tin lỗi.

Tìm các cảnh báo trong bảng điều khiển của bạn bao gồm CORBhoặc Cross-Origin Read Blocking.

Ngoài ra, cú pháp đã thay đổi để xử lý lỗi (như được mô tả trong mọi câu trả lời khác). Giờ đây, bạn sử dụng các toán tử có thể sử dụng ống dẫn, như sau:

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

0

Bằng cách sử dụng Interceptor, bạn có thể bắt lỗi. Dưới đây là mã:

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

Bạn có thể thích blog này hơn .. hãy xem ví dụ đơn giản cho nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.