【问题标题】:How to catch error response properly in Angular?如何在 Angular 中正确捕获错误响应?
【发布时间】:2021-06-24 13:44:27
【问题描述】:

我需要捕捉由我的 REST API 生成的响应主体,它使用自定义响应主体抛出 http 错误 406,但在我的角度,我只能捕捉到“不可接受”的标头状态,我无法获取身体,我怎样才能得到身体而不是“不可接受”的字符串?我也遇到了这个错误TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.,我不知道是哪个部分触发了这个错误。这是我的做法:

首先我创建全局服务类,它看起来像这样:

  constructor(
    private http: HttpClient,
    private alertx: AlertService) { }

    post<T>(url: string, body?: any, header?: HttpHeaders, param?: HttpParams): Observable<HttpResponse<T>>{
      return this.http.post<T>(url, body,
        {headers : header ? header : this.headers,
        observe: 'response',
        responseType: 'json',
        params: param})
        .pipe(catchError(err => {
          const error = err.error?.error_description || err.error?.message || err.statusText;
          console.log(err);
          console.log('here i am');
          return throwError(error);
        }))
        .pipe(timeout(10000), catchError(err => {
          if (err instanceof TimeoutError) {
            this.alertx.error('Timeout Exception');
            return throwError('Timeout Exception');
          }
        }))
        .pipe(shareReplay());
  }

我试图控制台记录错误,它只是出现字符串'不可接受',然后它继续控制台.log('here i am');它打印正确,我找不到TypeError的原因在哪里

这是我如何使用上面的函数:

  requestCancel(data: SelectItem, reasonCancel: string): Observable<any> {
      const request: RequestResponse<SelectItem> = new RequestResponse<SelectItem>(data);
      request.message = reasonCancel;
      return this.rest.post<any>(`${environment.apiUrl}${endpoint.sales_order.request_cancel}`, request).pipe(map(
          response => {
              return response.body.data;
          }));
  }

当我尝试 console.log 响应时,它没有出现。我认为我的函数在我的全局服务类的 post 函数中停止工作,我做错了什么?

------------------ 更新我的代码 --------------------强>

我创建 HttpRequestInterceptor 类来处理错误,这是我的类:

@Injectable()
export class HttpRequestInterceptor implements HttpInterceptor {

  user: UserModel;
  prevUrl: string = '';
  constructor(
    private loginSvc: LoginService, 
    private alertx: AlertService,
    private sharedService: SharedService) {
    this.sharedService.currentUserSubject.subscribe(user => this.user = user);
  }

  intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    this.sharedService.setLoading(true, httpRequest.url);
    return next.handle(httpRequest).pipe(catchError(err => {
      if ([401, 403].includes(err.status) /* && this.user */) {
        // auto logout if 401 or 403 response returned from api
        this.sharedService.setLoading(false, httpRequest.url);
        this.loginSvc.removeLocal();
      }
      this.sharedService.setLoading(false, httpRequest.url);
      const error = err.statusText || err.statusText + ' ' + err.error?.message || err.statusText + ' ' + err.error?.error_description;
      this.alertx.error(error, {autoClose: true});
      return throwError(err.error?.message);
    }))
      .pipe(map<HttpEvent<any>, any>((evt: HttpEvent<any>) => {
        if (evt instanceof HttpResponse) {
          this.sharedService.setLoading(false, httpRequest.url);
        }
        return evt;
      }));
  }
}

当我 console.log 我的err.error?.message 我可以正确捕获错误,然后我将值传递给这里:

    post<T>(url: string, body?: any, header?: HttpHeaders, param?: HttpParams): Observable<HttpResponse<T>>{
      return this.http.post<T>(url, body,
        {headers : header ? header : this.headers,
        observe: 'response',
        responseType: 'json',
        params: param})
        .pipe(catchError(err => {
          console.log(err);
          return throwError(err);
        }))
        .pipe(timeout(10000), catchError(err => {
          if (err instanceof TimeoutError) {
            this.alertx.error('Timeout Exception');
            return throwError('Timeout Exception');
          }
        }))
        .pipe(shareReplay());
  }

直到那里我仍然可以正确捕获错误,当我 console.log(err) 但是当我使用 post 函数时,我无法捕获错误,并且我在浏览器控制台中出现错误。这是我的功能:

  requestCancel(data: SelectItem, reasonCancel: string): Observable<any> {
      const request: RequestResponse<SelectItem> = new RequestResponse<SelectItem>(data);
      request.message = reasonCancel;
      return this.rest.post<any>(`${environment.apiUrl}${endpoint.sales_order.request_cancel}`, request).pipe(map(
          response => {
              return response.body.data;
          }))
          .pipe(catchError(err => {
              // i cannot get anything here
              console.log(err);
              return err;
      }));
  }

我无法记录我的错误,它不会打印任何内容,我认为我的函数崩溃并抛出此错误:

TypeError: You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

我还错过了什么?

【问题讨论】:

    标签: angular typescript observable


    【解决方案1】:

    您可以编写一个拦截器来在一个地方处理错误。这会更方便,因为您不必在不同的服务中处理类似的场景

    @Injectable({
      providedIn: 'root',
    })
    export class RequestInterceptor implements HttpInterceptor {
      public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(req).pipe(
          tap( // or catchError operator
            (event: HttpEvent<any>) => event,
            (error: any) => {
              if (error instanceof HttpErrorResponse) {
                if (error.status === 406) {
                  // do something here
                }
              }
            }
          )
        );
      }
    }
    

    并将 RequestInterceptor 添加到 AppModule 的提供者数组中

    @NgModule({
        imports: [
            ...
        ],
        declarations: [
            ...
        ],
        providers: [
            { provide: HTTP_INTERCEPTORS, useClass: RequestInterceptor, multi: true }
        ],
    
        bootstrap: [AppComponent]
    })
    export class AppModule {}
    

    【讨论】:

    • 我已经尝试过你的答案,但我仍然无法正确捕捉到错误
    • 您是否将 RequestInterceptor 添加到 AppModule 的提供者数组中?查看更新的答案
    • 我尝试了您的答案,但出现错误错误,此错误消息类似于“发生意外的服务器错误”。但我想得到真正的错误信息
    【解决方案2】:

    正如蒂莫西的回答所同意的那样,HttpInterceptor 可以方便地从一个地方处理错误,它提供了一种拦截HTTP 请求和响应的方法,以便在传递它们之前对其进行转换或处理。

    拦截器中可以实现两种用例。

    首先,retryHTTP 在抛出错误之前调用一次或多次。在某些情况下,例如,如果有超时,最好继续而不抛出异常。

    为此,请使用 RxJS 中的 retry 运算符重新订阅 observable。

    然后检查异常的状态,看看是不是401未授权的错误。然后检查异常的状态,看看是不是406不可接受的错误。使用基于令牌的安全性,如果需要,请尝试刷新令牌。如果这不起作用,请将用户重定向到登录页面,否则。

    import { Injectable } from '@angular/core';
    import { 
      HttpEvent, HttpRequest, HttpHandler, 
      HttpInterceptor, HttpErrorResponse 
    } from '@angular/common/http';
    import { Observable, throwError } from 'rxjs';
    import { retry, catchError } from 'rxjs/operators';
    
    @Injectable()
    export class ServerErrorInterceptor implements HttpInterceptor {
    
      intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    
        return next.handle(request).pipe(
          retry(1),
          catchError((error: HttpErrorResponse) => {
            if (error.status === 401) {
              // refresh token
            } 
            else if (error.status === 406) {
              // do something here
            } 
            else {
              return throwError(error);
            }
          })
        );    
      }
    }
    

    在您检查错误状态并重新抛出错误之前,您可以在这里retry 一次。如果需要,刷新安全令牌由您决定。

    【讨论】:

    • 我已经尝试过你的解决方案,但是当它抛出时我仍然无法捕捉到错误,我会更新我的问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-09-05
    • 2016-10-29
    • 1970-01-01
    • 1970-01-01
    • 2011-11-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多