首页 文章

在Angular HttpClient中捕获错误

提问于
浏览
59

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

@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 Error: Uncaught (in promise): [object Object] 来自 core.es5.js 如何在我的情况下处理它?

9 回答

  • 130

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

    打开here the working demo plunker以获取以下解决方案 .

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

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

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

    如果只需要在一个地方处理错误,可以使用 catch 并返回默认值(或空响应),而不是完全失败 . 你也不需要 .map 只是为了施放,你可以使用通用函数 . 资料来源:Angular.io - Getting Error Details .

    所以,一个通用的 .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上的服务状况不佳,处理错误也会使应用程序继续运行 .

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

    运行working demo plunker here .

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

    再次,Angular.io guide显示:

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

    当然,这可以用来以非常简单的方式处理错误(demo plunker here):

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

    Providing your interceptor: 简单地声明上面的 HttpErrorInterceptor 并不会导致您的应用使用它 . 您需要将它作为拦截器提供wire it up in your app module,如下所示:

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

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

    运行working demo plunker here .

  • 1

    随着 HTTPClient API的到来,不仅更换了 Http API,而且添加了一个新的 HttpInterceptor API .

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

    因此,假设您要添加 default error handling behavior ,将 .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,
    };
    

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

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

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

    如果您想要一个角度服务的好例子,请看一下at the following gist .

  • 0

    让我更新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;
          })
        );
      }
    }
    
  • 1

    你可能想要这样的东西:

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

    这在很大程度上取决于您如何使用您的服务,但这是基本情况 .

  • 2

    在@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 
                    }
               });
    
  • 14

    相当简单(与之前的API相比) .

    来自(复制并粘贴)Angular official guide的来源

    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!');
        }
      );
    
  • 42

    如果您发现自己无法捕获此处提供的任何解决方案的错误,则可能是服务器未处理CORS请求 .

    在那种情况下,Javascript,更不用说Angular,可以访问错误信息 .

    在控制台中查找包含 CORBCross-Origin Read Blocking 的警告 .

    此外,语法已更改以处理错误(如每个其他答案中所述) . 你现在使用管道运营商,如下:

    this.service.requestsMyInfo(payload).pipe(
        catcheError(err => {
            // handle the error here.
        })
    );
    
  • 0
    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

    通过使用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");
            }
          }
        });
      }
    }
    

    你可以更喜欢this blog ..给它一个简单的例子 .

相关问题