在Angular HttpClient中捕获错误

Las*_*nal 86 angular angular-httpclient

我有一个如下所示的数据服务:

@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)
    }
}
Run Code Online (Sandbox Code Playgroud)

如果我得到一个HTTP错误(即404),我得到一个讨厌的控制台消息: Error错误:未捕获的(在承诺):[目标对象]core.es5.js 我如何处理它在我的情况?

acd*_*ior 176

根据您的需要,您有一​​些选择.如果要基于每个请求处理错误,请catch在您的请求中添加一个.如果要添加全局解决方案,请使用HttpInterceptor.

在这里打开以下解决方案的工作演示插件.

TL;博士

在最简单的情况下,您只需添加一个.catch()或一个.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)
      );
Run Code Online (Sandbox Code Playgroud)

但是有更多细节,见下文.


方法(本地)解决方案:记录错误并返回回退响应

如果只需要在一个地方处理错误,则可以使用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>();
          });
     }
}
Run Code Online (Sandbox Code Playgroud)

即使URL上的服务状况不佳,处理错误也会使应用程序继续运行.

当您想要为每个方法返回特定的默认响应时,此每请求解决方案很有用.但是,如果您只关心错误显示(或具有全局默认响应),则更好的解决方案是使用拦截器,如下所述.

这里运行工作演示plunker.


高级用法:拦截所有请求或响应

Angular.io指南再次显示:

@angular/common/http截取的一个主要特征是能够声明位于应用程序和后端之间的拦截器.当您的应用程序发出请求时,拦截器会在将其发送到服务器之前对其进行转换,拦截器可以在应用程序看到之前将响应转换回来.从身份验证到日志记录,这非常有用.

当然,这可以用来以非常简单的方式处理错误(这里演示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>>();
      });
  }
}
Run Code Online (Sandbox Code Playgroud)

提供拦截器:简单地声明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 {}
Run Code Online (Sandbox Code Playgroud)

注意:如果同时存在错误拦截器和一些本地错误处理,则很可能不会触发本地错误处理,因为拦截器到达本地错误处理之前始终会处理错误.

这里运行工作演示plunker.

  • 好吧,如果他想完全看中,他会完全明白他的服务:`return this.httpClient.get <type>(...)`.然后在他实际消耗它的服务的某个地方"抓住......",因为他将构建可观察的流动并且能够最好地处理它. (2认同)
  • @YakovFain 如果你想在拦截器中有一个默认值,它必须是一个 `HttpEvent`,比如一个 `HttpResponse`。因此,例如,您可以使用:`return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));`。我已经更新了答案以明确这一点。此外,我创建了一个工作演示 plunker 来显示一切正常:https://plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview (2认同)

Jot*_*edo 45

随着HTTPClientAPI 的到来,API不仅被Http替换,而且还添加了一个新的HttpInterceptorAPI.

AFAIK的目标之一是为所有HTTP传出请求和传入响应添加默认行为.

因此,假设您要添加默认的错误处理行为,添加.catch()到所有可能的http.get/post/etc方法是非常难以维护的.

这可以通过以下方式完成,例如使用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,
};
Run Code Online (Sandbox Code Playgroud)

// app.module.ts

import { ErrorInterceptorProvider } from 'somewhere/in/your/src/folder';

@NgModule({
   ...
   providers: [
    ...
    ErrorInterceptorProvider,
    ....
   ],
   ...
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Promises从您的服务方法返回而不是Observables另一个错误的决定.

还有一条建议:如果你使用TYPE脚本,那么就开始使用它的类型部分.您将失去该语言的最大优势之一:了解您正在处理的值的类型.

如果你想要一个角度服务的好例子,请看看下面的要点.


Meg*_*per 36

让我更新acdcjunior关于使用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;
      })
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 这需要得到更多的支持。acdcjunior的答案到今天为止无法使用 (7认同)

Udi*_*tha 19

对于 Angular 6+ , .catch 不能直接与 Observable 一起使用。你必须使用

.pipe(catchError(this.errorHandler))
Run Code Online (Sandbox Code Playgroud)

下面的代码:

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.');
  }
}
Run Code Online (Sandbox Code Playgroud)

有关更多详细信息,请参阅HttpAngular 指南

  • 这是唯一对我有用的答案。其他人给出错误:“类型‘Observable&lt;unknown&gt;’不可分配给类型‘Observable&lt;HttpEvent&lt;any&gt;&gt;”。 (2认同)

Cod*_*Spy 6

Angular 8 HttpClient 错误处理服务示例

在此处输入图片说明

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

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

    }
Run Code Online (Sandbox Code Playgroud)


Tom*_*mer 5

相当简单(与以前的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!');
    }
  );
Run Code Online (Sandbox Code Playgroud)


小智 5

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)));
}
Run Code Online (Sandbox Code Playgroud)