捕获角度 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)
}
}

如果我得到一个 HTTP 错误(比如404) ,我会得到一个讨厌的控制台消息: 错误错误: 未捕获(在承诺中) : [ Object Object ] from < strong > core.es5.js 我该怎么处理我的案子?

447156 次浏览

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

打开 这里的工作演示柱塞查看下面的解决方案。

博士

在最简单的情况下,您只需要添加一个 .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来强制转换,您可以使用一个通用函数。资料来源: 获取错误详细信息

因此,一个通用的 .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 {}

注意: 如果您有一个 都有错误拦截器和一些本地错误处理,那么很自然地,很可能不会触发任何本地错误处理,因为错误总是由拦截器 之前处理,所以它会到达本地错误处理。

运行 工作演示柱塞在这里

你可能想要这样的东西:

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

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

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

随着 HTTPClient API 的到来,不仅取代了 Http API,而且增加了一个新的,即 HttpInterceptor API。

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是另一个糟糕的决定。

还有一条额外的建议: 如果您正在使用 类型script,那么就开始使用它的类型部分。您将失去该语言的最大优点之一: 知道所处理的值的类型。

如果你想要一个,在我看来,一个角服务的好例子,看看 大意如下

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

来源(复制和粘贴)的 有角度的官方指南

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

如果您发现自己无法使用这里提供的任何解决方案捕获错误,那么可能是服务器没有处理 CORS 请求。

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

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

此外,处理错误的语法也发生了变化(正如每个其他答案中所描述的那样)。现在可以使用管道运算符,如下所示:

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

请允许我用最新的 RxJs 特性(v. 6)更新 儿子关于使用 HttpInterceptor的答案。

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

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

通过使用拦截器,你可以捕获错误。下面是代码:

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

你可以选择 这个博客. . 给出一个简单的例子。

对于角度6 + ,. catch 不能直接与 Observer 一起工作

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

有关详细信息,请参阅 Http 的角度指南

角8 HttpClient 错误处理服务 例子

enter image description here

Api 服务中心

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


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


}

更糟糕的事情是没有一个像样的堆栈跟踪,您根本无法使用 HttpInterceptor生成它(希望能够得到纠正)。您得到的只是一个 zone 和 rxjs 无用的臃肿负载,而不是生成错误的行或类。

为此,您需要在扩展的 HttpClient中生成一个堆栈,因此在生产环境中这样做是不明智的。

/**
* Extended HttpClient that generates a stack trace on error when not in a production build.
*/
@Injectable()
export class TraceHttpClient extends HttpClient {
constructor(handler: HttpHandler) {
super(handler);
}


request(...args: [any]): Observable<any> {
const stack = environment.production ? null : Error().stack;
return super.request(...args).pipe(
catchError((err) => {
// tslint:disable-next-line:no-console
if (stack) console.error('HTTP Client error stack\n', stack);
return throwError(err);
})
);
}
}

在角度13

      this.callApiSrvice.GetProducts()
.subscribe({
next: (data) =>
{
this.products = data;
},
error: (e) =>
{
        

console.log('Error while loading the product data');
}
})