上节的情况下,分别在班级管理、个人中心、注销时发生了401错误。虽然我们可以在三个组件中分别加入判断是否发生401错误的代码,但这么做明显是在造重复的轮子。 ```typescript this.httpClient.get(url) .subscribe(() => console.log('success'), error => 判断是否发生了401,发生401则注销应用,显示登录窗口); ``` ## 再谈拦截器 继续开始之前,我们需要再复习下拦截器: ![image-20210309104836454](https://img.kancloud.cn/1b/86/1b86f3c365fbc64f972e3a6c7e8734d4_1474x456.png) 通过上图不难发现,拦截器不但可以对请求进行拦截,还可以对后台的响应进行拦截。 在前面的章节中,我们其实已经偷偷的说明了后期要加入一个登录的拦截器了: ![image-20210309110421080](https://img.kancloud.cn/11/b7/11b78fa5cc2228b8257f6e983ac6d590_1714x868.png) ## 拦截异常 在`XAuthTokenInterceptor`中我们已经初步的掌握了使用`tap()`操作符对响应进行监听的方法: ```typescript return next.handle(request).pipe(tap(input => { // 仅当input类型为HttpResponseBase,才尝试获取token并更新 if (input instanceof HttpResponseBase) { const httpHeader = input.headers; const xAuthToken = httpHeader.get('x-auth-token'); if (xAuthToken !== null) { this.setToken(xAuthToken); } } })); ``` 本例中,我们将使用一个新的操作符`catchError()`来监听后台响应数据是否为**认证失败**。 ### 初始化 来到`src/app`文件夹,初始化`UnAuth`拦截器: ```bash panjie@panjies-iMac app % pwd /Users/panjie/github/mengyunzhi/angular11-guild/first-app/src/app panjie@panjies-iMac app % ng g interceptor unAuth CREATE src/app/un-auth.interceptor.spec.ts (417 bytes) CREATE src/app/un-auth.interceptor.ts (411 bytes) ``` ### 应用拦截器 有了拦截器后,我们将其添加到App模块中以使其生效: ```typescript +++ b/first-app/src/app/app.module.ts @@ -16,6 +16,7 @@ import {XAuthTokenInterceptor} from './x-auth-token.interceptor'; import {WelcomeComponent} from './welcome.component'; import { NavComponent } from './nav/nav.component'; import {ApiInterceptor} from './api.interceptor'; +import {UnAuthInterceptor} from './un-auth.interceptor'; @NgModule({ @@ -39,7 +40,8 @@ import {ApiInterceptor} from './api.interceptor'; ], providers: [ {provide: HTTP_INTERCEPTORS, useClass: XAuthTokenInterceptor, multi: true}, - {provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: ApiInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: UnAuthInterceptor, multi: true} ], bootstrap: [IndexComponent] }) ``` ### catchError 我们在编写程序时会遇到异常,通常一些可见的异常使用`try catch`来解决。RxJS借鉴了这一思想。当数据流没有按预期返回时,将向上抛出异常。打个现实生活中可能没有的比方,我们关注了某个喜欢的人,此后我们将会收到该人发布的新状态,但有一天这个不走寻常路是被人**发现**了,自此该账号被永久封禁。在此事件中,这个永久封禁便是个异常事件。在该账号被封的时候我们将得到一个某账号被封的通知,这便是RxJS中的异常。 在Angular的Http请求中,将两种情况视为异常: 1. 服务端的非2XX错误,比如资源未找到时发生的404错语,用户认证失败时发生的401错语,权限校验时发生的403错误等。 2. 客户端发生了网络错误,比如当前计算机处理脱机状态,无法访问外部网络;或是当前计算机的网卡因为驱动的问题而未正常工作等。 我们在此仅考虑第1种情况,服务端的非2xx错误。 与正常数据可以使用`tap()`监听一样,异常的数据可以`catchError()`操作符来监听。与`tap()`操作符并不要求有返回值不同,`catchError()`操作符要求必须有返回值,该返回值可以通过`RxJS`提供的`throwError()`方法来快速实现: ```typescript +++ b/first-app/src/app/un-auth.interceptor.ts @@ -5,7 +5,8 @@ import { HttpEvent, HttpInterceptor } from '@angular/common/http'; -import {Observable} from 'rxjs'; +import {Observable, throwError} from 'rxjs'; +import {catchError} from 'rxjs/operators'; @Injectable() export class UnAuthInterceptor implements HttpInterceptor { @@ -14,6 +15,11 @@ export class UnAuthInterceptor implements HttpInterceptor { } intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { - return next.handle(request); + return next.handle(request) + .pipe(catchError(error => { ① + console.log('发生了错误', error); + // 使用throwError()继续向上抛出异常 + return throwError(error); ② + })); } } ``` - ① `catchError`操作符同样接收回调函数做为参数 - ② 使用`throwError()`方法向上继续抛出异常 此时当点击注销时(需要按上节的步骤,模拟半个小时没有登录),将在控制台中发现如下日志: ![image-20210409163335140](https://img.kancloud.cn/fb/a3/fba38f093116527cb6b157c137a5e9a6_3192x140.png) 这其中的`status: 401`就是我们想要进行判断的返回状态码。 ```typescript +++ b/first-app/src/app/un-auth.interceptor.ts @@ -17,7 +17,9 @@ export class UnAuthInterceptor implements HttpInterceptor { intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> { return next.handle(request) .pipe(catchError(error => { - console.log('发生了错误', error); + if (error.status === 401) { + console.log('发生了401错误, 通知应用显示登录界面', error); + } // 使用throwError()继续向上抛出异常 return throwError(error); })); ``` 此时一旦发生401错误,则会在控制台中打印一条相关日志。401被判断出来后,通知应用这个状态则可以实现:当用户因长时间未操作而发生被动注销时,及时地显示登录界面了。 下一小节中,我们将建立一个服务,并将其分别注入到拦截器及`Index`组件,并以该服务为纽带,在请求发生401时将通知发送给`Index`组件。 | 名称 | 链接 | | ------------ | ------------------------------------------------------------ | | 处理请求错误 | [https://angular.cn/guide/http#handling-request-errors](https://angular.cn/guide/http#handling-request-errors) | | 本节源码 | [https://github.com/mengyunzhi/angular11-guild/archive/step6.7.1.zip](https://github.com/mengyunzhi/angular11-guild/archive/step6.7.1.zip) |