Angular 框架中内置了通过 HTTP/HTTPS 访问后端服务的模块 HttpClientModule, 通过模块中的 HttpClient 对象,开发者可以轻松的实现访问后端的 restful 接口。因为 HttpClient 采用了响应式的编程风格(rxjs),所以很多初学者对于如何做错误处理会比较模糊,本文试图通过一些例子给读者一些指引。

subscribe 方法中的错误处理

我们知道, 在调用 HttpClient 的网络访问方法(比如: GET, POST, PUT 等)时会返回一个 Observable 的对象,通过调用改对象上的 subscribe 方法可以拿到返回的结果。如在课堂案例中,访问后端登录接口的代码如下:

login.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
login() {
console.log('login, userId = ' + this.userId + ', pwd = ' + this.pwd);
let ob = this.securitySrv.login(this.userId, this.pwd);

ob.subscribe(res => {
if (res['succ']) {
console.log('success');
this.securitySrv.token = res['data'];
this.router.navigate(["home"]);
} else {
console.log('fail');
}
});
}

security-srv.service.ts

1
2
3
login(username: string, password: string) {
return this.http.post(`#123;this.BASE_URL}/api/login`, { userId: username, password: password });
}

以上的组件代码(login.component.ts)的第3行和第5行,是为了突出在组件调用中生成了 Observable 对象才分开写的,通常的写法时连在一起,如下:

1
2
3
4
5
6
7
8
9
10
11
12
login() {
console.log('login, userId = ' + this.userId + ', pwd = ' + this.pwd);
let ob = this.securitySrv.login(this.userId, this.pwd).subscribe(res => {
if (res['succ']) {
console.log('success');
this.securitySrv.token = res['data'];
this.router.navigate(["home"]);
} else {
console.log('fail');
}
});
}

注意:对于业务可识别的错误,我们一般采用判断返回对象中 succ 属性的值来判断,如以上代码中的第4行到第10行所展示的

为了处理技术性错误,比如网络不能访问,跨域错误等等,我们可以采用 subscribe 内置的错误处理来完成。修改 login.component.ts 中的 login 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
logi() {
console.log('login, userId = ' + this.userId + ', pwd = ' + this.pwd);

let ob = this.securitySrv.login(this.userId, this.pwd);

ob.subscribe(res => {
if (res["succ"]) {
this.securitySrv.token = res["data"];
this.router.navigate(["home"]);
} else {
console.log('fail');
}
},
error => {
console.log(error)
alert('网络错误')
})
}

可以看到,我们调用 subscribe 方法时,多加了一个参数,该参数是一个箭头函数,参数为调用时的错误信息对象 error。然后就可以在箭头函数的代码段完成处理处理,比如提示用户,记录错误信息等。

对错误进行分类处理

如果我们希望得到错误的详细信息或是按错误的类别进行分类处理,那又要怎么做呢?

实际上 error 对象是 HttpErrorResponse 的实例,包含了错误的信息信息。

1
HttpErrorResponse {headers: HttpHeaders, status: 0, statusText: "Unknown Error", url: "https://uat-1.hohistar.com.cn1/api/login", ok: false, …}

这样我们就可以根据 HttpErrorResponse 中的相关信息,进行分类处理。 示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  login() {
console.log('login, userId = ' + this.userId + ', pwd = ' + this.pwd);

let ob = this.securitySrv.login(this.userId, this.pwd);

ob.subscribe(res => {
if (res["succ"]) {
this.securitySrv.token = res["data"];
this.router.navigate(["home"]);
} else {
console.log('fail');
}
},
error => {
console.log(error)
if (error.error instanceof ErrorEvent) {
console.error('错误: ', error.error.message);
} else {
console.error(`执行业务方法出错: #123;error.status} - #123;error.error}`)
}
})
}
}

在Service中处理错误

在实际项目中建议将错误处理放在 Service 中来完成,在界面上 (Component) 中应该只需要简单的显示错误即可, 在 HttpClient 的响应式编程中我们也可以非常轻松过的实现。 在 Service 中定义一个错误处理的私有方法,在该方法中对 error 进行分类处理,如果需要提示用户,则使用 showError 将信息包装为 error 抛出给 Component。然后通过 rxjs 中的 pipe, 将该处理函数加入到整个处理中。 改造后的 Service 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private handlerError(error: HttpErrorResponse) {
console.log('service handle error: ' + error);
return throwError('服务器无法访问');
}


login(username: string, password: string) {
// return this.http.post(`#123;this.BASE_URL}/api/login`, { userId: username, password: password });
return this.http.post(`#123;this.BASE_URL}/api/login`, { userId: username, password: password })
.pipe(
catchError(this.handlerError)
);
}

因为在 Service 进行了错误进行了处理,所以 Component 中只需要简单的截获显示错误信息即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
login() {
console.log('login, userId = ' + this.userId + ', pwd = ' + this.pwd);

let ob = this.securitySrv.login(this.userId, this.pwd);

ob.subscribe((res: ApiResult<string>) => {
if (res["succ"]) {
this.securitySrv.token = res["data"];
this.router.navigate(["home"]);
} else {
console.log('fail');
}
},
error => {
alert(error)
})
}

注意最后的箭头函数, 在这里截获的错误(error)是已经经过转换的error, 包含了可以显示给用户的信息,所以直接显示即可