Angular HttpClient에서 오류 잡기


114

다음과 같은 데이터 서비스가 있습니다.

@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 오류 : Uncaught (in promise) : [object Object] from core.es5.js 내 경우 어떻게 처리합니까?

답변:


231

필요에 따라 몇 가지 옵션이 있습니다. 요청별로 오류를 처리하려면 요청에를 추가 catch하십시오. 글로벌 솔루션을 추가하려면 HttpInterceptor.

열고 여기에 작업 데모 plunker 아래의 솔루션을.

tl; dr

가장 간단한 경우, 당신은 단지 추가해야합니다 .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의 서비스 상태가 좋지 않은 경우에도 앱을 계속할 수 있습니다.

이 요청 별 솔루션은 주로 각 메서드에 특정 기본 응답을 반환하려는 경우에 유용합니다. 그러나 오류 표시에만 관심이 있거나 전역 기본 응답이있는 경우 더 나은 해결책은 아래에 설명 된대로 인터셉터를 사용하는 것입니다.

여기 에서 작동하는 데모 플 런커를 실행 하십시오 .


고급 사용법 : 모든 요청 또는 응답 차단

다시 한번 Angular.io 가이드 는 다음을 보여줍니다.

의 주요 기능 @angular/common/http은 차단, 즉 애플리케이션과 백엔드 사이에있는 인터셉터를 선언하는 기능입니다. 애플리케이션이 요청을 할 때 인터셉터는이를 서버로 보내기 전에 변환하고 인터셉터는 애플리케이션이 응답을보기 전에 응답을 변환 할 수 있습니다. 이것은 인증에서 로깅까지 모든 것에 유용합니다.

물론 매우 간단한 방법으로 오류를 처리하는 데 사용할 수 있습니다 ( 여기에서 데모 플 런커 ).

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

인터셉터 제공 :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 {}

참고 : 오류 인터셉터와 일부 로컬 오류 처리 가 모두 있는 경우 , 로컬 오류 처리에 도달 하기 전에 오류가 항상 인터셉터에 의해 처리 되기 때문에 당연히 로컬 오류 처리가 트리거되지 않을 가능성이 높습니다 .

여기 에서 작동하는 데모 플 런커를 실행 하십시오 .


2
글쎄요, 그가 완전히 화려 해지기를 원한다면 그는 그의 서비스를 완전히 분명하게 남겨 둘 것입니다 : return this.httpClient.get<type>(...). 그리고 catch...그가 실제로 그것을 소비하는 서비스의 어딘가에 있어야 합니다. 왜냐하면 그가 Observable 흐름을 구축하고 가장 잘 처리 할 수있는 곳이기 때문입니다.
dee zg

1
동의합니다. 최적의 해결책은 Promise<Object>의 클라이언트 ( DataService의 메서드 호출자 )가 오류를 처리하도록하는 것입니다. 예 : this.dataService.post('url', {...}).then(...).catch((e) => console.log('handle error here instead', e));. 귀하와 귀하의 서비스 사용자에게 더 명확한 것을 선택하십시오.
acdcjunior 2017 년

1
컴파일되지 않습니다. return Observable.of({my: "default value..."}); "| ... 'is not assignable to type'HttpEvent <any> '."오류가 발생합니다.
Yakov Fain 2017

1
당신이 요격에 기본 값을 원하는 경우 @YakovFain, 그것은해야합니다 HttpEvent같은 등을 HttpResponse. 예를 들어 다음을 사용할 수 있습니다 return Observable.of(new HttpResponse({body: [{name: "Default value..."}]}));.. 이 점을 명확히하기 위해 답변을 업데이트했습니다. : 또한, 내가 보여 데모 plunker 작업 모든 가공 만들어 plnkr.co/edit/ulFGp4VMzrbaDJeGqc6q?p=preview
acdcjunior

1
@acdcjunior, 당신은 :)주는 계속 선물입니다
LastTribunal

67

최신 RxJs 기능 (v.6)으로 HttpInterceptor를 사용하는 것에 대한 acdcjunior 의 답변을 업데이트하겠습니다 .

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
이것은 더 많은 찬성 투표가 필요합니다. acdcjunior의 대답은 오늘로 사용할 수 없습니다
폴 크루거

48

HTTPClientAPI 가 등장 하면서 HttpAPI가 교체 되었을 뿐만 아니라 새로운 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,
};

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

Promises대신 서비스 방법에서 돌아 오는 Observables것은 또 다른 나쁜 결정입니다.

추가 조언 : TYPE 스크립트를 사용하는 경우 해당 유형 부분을 사용하십시오. 당신은 언어의 가장 큰 장점 중 하나를 잃게됩니다 : 당신이 다루는 가치의 유형을 아는 것입니다.

내 생각에 각도 서비스의 좋은 예를 원한다면 다음 요점을 살펴보십시오 .


의견은 확장 된 토론을위한 것이 아닙니다. 이 대화는 채팅 으로 이동 되었습니다 .
deceze

나는이 있어야합니다 가정 this.http.get()등을하지 this.get()등의 DataService?
displayname

선택한 답변이 이제 더 완성 된 것으로 보입니다.
Chris Haines

9

상당히 간단합니다 (이전 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!');
    }
  );

9

Angular 6+의 경우 .catch는 Observable에서 직접 작동하지 않습니다. 당신은 사용해야합니다

.pipe(catchError(this.errorHandler))

아래 코드 :

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

자세한 내용은 Angular Guide for Http를 참조하십시오.


1
이것은 나를 위해 일한 유일한 대답입니다. 다른 오류는 " 'Observable <unknown>'유형을 'Observable <HttpEvent <any >>"유형에 할당 할 수 없습니다.
아서 왕 3 세

5

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

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

    }

2

아마도 다음과 같은 것을 원할 것입니다.

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

서비스를 어떻게 사용 하느냐에 따라 크게 다르지만 이것이 기본적인 경우입니다.


1

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

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

여기에 제공된 솔루션으로 오류를 포착 할 수없는 경우 서버가 CORS 요청을 처리하지 않는 것일 수 있습니다.

이 경우 Angular보다 훨씬 적은 Javascript가 오류 정보에 액세스 할 수 있습니다.

포함 콘솔에서 경고를 찾으 CORBCross-Origin Read Blocking.

또한 오류 처리를 위해 구문이 변경되었습니다 (다른 모든 답변에 설명 됨). 이제 다음과 같이 파이프 가능 연산자를 사용합니다.

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

0

인터셉터를 사용하면 오류를 포착 할 수 있습니다. 다음은 코드입니다.

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

이 블로그 를 선호 할 수 있습니다 . .. 간단한 예제가 제공됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.