【问题标题】:Http Error Handling in Angular 6Angular 6 中的 Http 错误处理
【发布时间】:2018-11-30 21:34:42
【问题描述】:

我正在尝试使用 Angular 6 中的以下类来处理 http 错误。我从服务器获得了 401 未授权状态。但是我没有看到控制台错误消息。

HttpErrorsHandler.ts 文件

import { ErrorHandler, Injectable} from '@angular/core';
    @Injectable()
    export class HttpErrorsHandler implements ErrorHandler {
      handleError(error: Error) {
         // Do whatever you like with the error (send it to the server?)
         // And log it to the console
         console.error('It happens: ', error);
      }
    }

app.module.ts 文件

providers: [{provide: ErrorHandler, useClass: HttpErrorsHandler}],

HttpCallFile

import { Injectable , Component} from '@angular/core';
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { Observable } from 'rxjs';
import {AuthServiceJwt} from '../Common/sevice.auth.component';

@Injectable()
export class GenericHttpClientService {
    private readonly baseUrl : string = "**********";


    constructor(private httpClientModule: HttpClient , private authServiceJwt : AuthServiceJwt)  {
    }

    public GenericHttpPost<T>(_postViewModel: T , destinationUrl : string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json; charset=utf-8')
            .set('Authorization',`Bearer ${this.authServiceJwt.getToken}`);
        return this.httpClientModule.post<T>(this.baseUrl + destinationUrl, _postViewModel, { headers });
    }

    // This method is to post Data and Get Response Data in two different type
    public GenericHttpPostAndResponse<T,TE>(postViewModel: TE, destinationUrl: string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json; charset=utf-8')
            .set('Authorization',`Bearer ${this.authServiceJwt.getToken}`);
        return this.httpClientModule.post<T>(this.baseUrl + destinationUrl, postViewModel, { headers });
    }

    // This method is to post Data and Get Response Data in two different type without JWT Token
    public GenericHttpPostWithOutToken<T,TE>(postViewModel: TE, destinationUrl: string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json; charset=utf-8');
        return this.httpClientModule.post<T>(this.baseUrl + destinationUrl, postViewModel, { headers });
    }

    public GenericHttpGet<T>(destinationUrl: string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json')
            .set('Authorization',`Bearer ${this.authServiceJwt.getToken}`);
        return this.httpClientModule.get<T>(this.baseUrl + destinationUrl, { headers });
    }

    public GenericHttpDelete<T>(destinationUrl: string): Observable<T> {
        const headers = new HttpHeaders().set('Content-Type', 'application/json')
            .set('Authorization',`Bearer ${this.authServiceJwt.getToken}`);
        return this.httpClientModule.delete<T>(this.baseUrl + destinationUrl, { headers });
    }
}

admin.user.component.ts 文件

private getUsersHttpCall(): void {
    this.spinnerProgress = true;
    this.genericHttpService.GenericHttpGet<GenericResponseObject<UserViewModel[]>>(this.getAdminUserUrl).subscribe(data => {
      if (data.isSuccess) {
        this.genericResponseObject.data = data.data;
        this.dataSource = this.genericResponseObject.data
        this.spinnerProgress = false;
      }

    }, error => {
      console.log(error);
      this.spinnerProgress = false;
    });
  }

【问题讨论】:

  • 你应该使用拦截器(HttpInterceptor)而不是服务。
  • 你在admin.user.component.ts里面做什么?您似乎正在处理那里的错误。
  • 我,正在从 admin.user.component.ts 文件调用 http 调用。不处理该文件中的错误。更新代码请看

标签: angular typescript angular-routing angular6 angular-errorhandler


【解决方案1】:

对于 XHR 请求,您应该使用 Interceptor

这是我用来将 JWT 添加到标头并处理一些响应错误的方法:

import {Injectable} from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor, HttpErrorResponse
} from '@angular/common/http';
import {AuthService} from '../service/auth.service';
import {Observable, of} from 'rxjs';
import {Router} from "@angular/router";
import {catchError} from "rxjs/internal/operators";

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

  constructor(public auth: AuthService, private router: Router) {
  }


  /**
   * intercept all XHR request
   * @param request
   * @param next
   * @returns {Observable<A>}
   */
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    if (localStorage.getItem('jwtToken')) {
      request = request.clone({
        setHeaders: {
          Authorization: 'Bearer ' + localStorage.getItem('jwtToken')
        }
      });
    }

    /**
     * continues request execution
     */
    return next.handle(request).pipe(catchError((error, caught) => {
        //intercept the respons error and displace it to the console
        console.log(error);
        this.handleAuthError(error);
        return of(error);
      }) as any);
  }


  /**
   * manage errors
   * @param err
   * @returns {any}
   */
  private handleAuthError(err: HttpErrorResponse): Observable<any> {
    //handle your auth error or rethrow
    if (err.status === 401) {
      //navigate /delete cookies or whatever
      console.log('handled error ' + err.status);
      this.router.navigate([`/login`]);
      // if you've caught / handled the error, you don't want to rethrow it unless you also want downstream consumers to have to handle it as well.
      return of(err.message);
    }
    throw err;
  }
}

不要忘记将拦截器注册到app.module.ts,如下所示:

import { TokenInterceptor } from './auth/token.interceptor';

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

【讨论】:

  • 你必须从 rxjs 导入。如果您使用的是 ide,它应该会建议您
  • 这会吃掉不是 401|403 错误的 HTTP 错误。如果错误代码与错误代码不匹配,您必须throw,以便其他.pipe 可以处理它。此外,不应处理 403 错误;仅仅因为用户尝试了他们被禁止做的事情,并不意味着他们需要重新登录。
  • @amphetamachine 你为什么这么说?
  • @firegloves 我的服务会添加自己的.pipe 来处理其 API 调用的错误。说return of(error); 隐含地使它无法将非401 错误传递给服务的.pipe。使用您的示例,我通过说if (error.status === 401) { this.handleAuthError(error); return of(error); } throw error; 使我的代码适用于这两种类型的错误
  • 我得到了这个工作。 handleAuthError() 应该以“throw err”结尾,而不是“throw error”。
【解决方案2】:

根据@firegloves 的回答,为了使各个服务中的.pipe 处理程序实际上能够catchError 他们自己的HTTP 失败代码,您需要将代码构造为:

  1. 如果拦截器检测到 HTTP 401 错误代码,则对其进行处理并返回 of(error),这可能会在任何后续 .pipe 中正常处理。
  2. 如果 HTTP 错误代码是 Interceptor 无法处理的错误代码,throw 再次处理它,以便后续错误处理程序(如您的服务中的错误处理程序)可以为自己的调用拾取和处理非 401 错误。

我的拦截器有双重工作。它:

  1. 如果已存储令牌,则将Authorization 标头注入所有传出请求。
  2. 拦截 HTTP 401 未经身份验证的错误,此时它会使令牌存储无效,然后重定向回 /login
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import {
    HttpErrorResponse,
    HttpEvent,
    HttpHandler,
    HttpInterceptor,
    HttpRequest,
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';

import { AuthService } from './auth.service';

@Injectable({
    providedIn: 'root',
})
export class RequestInterceptor implements HttpInterceptor {

    constructor(
        private readonly auth: AuthService,
        private readonly router: Router,
    ) {
    }

    /**
     * @param HttpRequest<any> request - The intercepted request
     * @param HttpHandler next - The next interceptor in the pipeline
     * @return Observable<HttpEvent<any>>
     */
    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        request = this.addToken(request);
        return next.handle(request)
            // add error handling
            .pipe(
                catchError(
                    (error: any, caught: Observable<HttpEvent<any>>) => {
                        if (error.status === 401) {
                            this.handleAuthError();
                            // if you've caught / handled the error, you don't
                            // want to rethrow it unless you also want
                            // downstream consumers to have to handle it as
                            // well.
                            return of(error);
                        }
                        throw error;
                    }
                ),
            );
    }

    /**
     * Handle API authentication errors.
     */
    private handleAuthError() {
        // clear stored credentials; they're invalid
        this.auth.credentials = null;
        // navigate back to the login page
        this.router.navigate(['/login']);
    }

    /**
     * Add stored auth token to request headers.
     * @param HttpRequest<any> request - the intercepted request
     * @return HttpRequest<any> - the modified request
     */
    private addToken(request: HttpRequest<any>): HttpRequest<any> {
        const token: string = this.auth.token;
        if (token) {
            return request.clone({
                setHeaders: {
                    Authorization: `Bearer ${token}`,
                },
            });
        }
        return request;
    }

}

AuthService 所做的只是有一个公共 get/set 将凭据对象粘贴到 localStorage - 它确保令牌没有过期,但您可以随意设计它。

就像上面提到的@firegloves,你必须在app.module.ts的管道中添加拦截器:

import { RequestInterceptor } from './auth/request.interceptor';

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

【讨论】:

  • 我如何将错误对象(但不将其抛出)返回给 API 调用函数?
  • @ZhuHang 要处理非 401 错误,在 .service.component 中,将 .pipe(catchError((err, caught) =&gt; {})) 添加到 http 可观察链,它应该会收到错误包。
  • 但是如果我使用await xxx.toPromise(),我还能得到错误对象吗?
【解决方案3】:

角度 7 中的示例错误处理

public getstats(param: string, confgId: number, startDate: Date, endDate: Date): Observable<string> {
return this.http.post(this.stats, {
  param: param,
  endDate: endDate
}).pipe(tap((stats: string) => console.log('Got Exceedance Stats : ' + JSON.stringify(stats))),
  catchError(this.handleError<string>('stats')));

}

下面是错误处理程序

private handleError<T>(operation = 'operation', result?: T) {
return (error: any): Observable<T> => {
  console.error(error);   
  console.log(`${operation} failed: ${error.message}`);
  return of(result as T);
};

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-16
    • 2016-07-12
    • 2017-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-09
    • 1970-01-01
    相关资源
    最近更新 更多