RxJS 初识
RxJS 是一个组织异步逻辑的库,它有很多 operator,可以极大的简化异步逻辑的编写。
它是由数据源(observable)产生数据,经过一系列 operator 的处理,最后传给接收者。
比如这样:
调用 of 操作符创建一个 Observable,它发送了三个值 1、2、3。
使用 pipe 方法,将 map 操作符添加到 Observable 上,对每个输入值进行平方运算,得到一个新的 Observable,它发送了 1、4、9。
再次使用 pipe 方法,将 filter 操作符添加到 Observable 上,过滤掉所有偶数值,得到一个新的 Observable,它发送了 1、9。
最后,调用 subscribe 方法订阅这个 Observable,当它发送新值时,调用提供的回调函数输出结果。在这个例子中,回调函数输出了每个接收到的值。
继续看这段代码:
调用 of 操作符创建一个 Observable,它发送了三个值 1、2、3。
使用 pipe 方法,将 scan 操作符添加到 Observable 上,对每个输入值进行累加操作,得到一个新的 Observable,它在每次接收到值时都会发出累加结果。在这个例子中,第一个值是 1,第二个值是 1 + 2 = 3,第三个值是 1 + 2 + 3 = 6。
再次使用 pipe 方法,将 map 操作符添加到 Observable 上,对每个累加结果进行平均数计算,得到一个新的 Observable,它在每次接收到值时都会发出平均数结果。在这个例子中,第一个值是 1,第二个值是 (1 + 2) / 2 = 1.5,第三个值是 (1 + 2 + 3) / 3 = 2。
最后,调用 subscribe 方法订阅这个 Observable,当它发送新值时,调用提供的回调函数输出结果。在这个例子中,回调函数输出了每个接收到的平均数结果。
再来看节流、防抖:
可以在官网文档看到所有的 operator。
如果异步逻辑复杂度高, RxJS 收益还是很高的。
Nest 的 interceptor 集成了 RxJS,可以用它来处理响应。
拦截器初识
创建一个测试项目:
bash
nest new interceptor-test -p npm
进入目录执行:
bash
nest g interceptor logging --flat --no-spec
记录下请求时间:
使用拦截器的方式:
方法级别:
访问页面,控制台打印:
全局:
typescript
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { LoggingInterceptor } from './logging.interceptor';
@Module({
// ...
providers: [
{
provide: APP_INTERCEPTOR,
useClass: LoggingInterceptor,
},
],
})
export class AppModule {}
控制器级别:
typescript
import { Controller, UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';
@Controller('cats')
@UseInterceptors(LoggingInterceptor)
export class CatsController {
// ...
}
我们再来看看适合在 Nest 的 interceptor 里用的 operator:
Nest 中使用 RxJS
map
map 操作符允许对从请求处理程序返回的数据进行转换。例如,可以使用它将数据包装在一个标准的响应对象中,该对象包含状态码、消息和数据。
生成一个 interceptor:
bash
nest g interceptor map-test --flat --no-spec
使用 map operator 对 controller 返回的数据做一些修改:
javascript
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { map, Observable } from 'rxjs';
@Injectable()
export class MapTestInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => {
return {
code: 200,
message: 'success',
data,
};
}),
);
}
}
controll 下启用:
请求接口:
tap
tap 操作符允许在不修改数据流的情况下执行副作用操作,比如记录日志或更新缓存。
这对于在请求处理过程中添加额外的日志记录或调试信息非常有用。
再生成个 interceptor
bash
nest g interceptor tap-test --flat --no-spec
使用 tap operator 来添加一些日志、缓存等逻辑:
javascript
import { AppService } from './app.service';
import {
CallHandler,
ExecutionContext,
Injectable,
Logger,
NestInterceptor,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class TapTestInterceptor implements NestInterceptor {
constructor(private appService: AppService) {}
private readonly logger = new Logger(TapTestInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
tap((data) => {
// 这里假装下更新缓存的操作
this.appService.getHello();
this.logger.log(`log something`, data);
}),
);
}
}
在 controller 返回响应的时候记录一些东西。
catchError
catchError 操作符允许处理在请求处理程序中抛出的异常。
可以先在拦截器中捕获这些异常,并执行一些额外的逻辑,比如记录错误日志或返回自定义的错误响应。
生成 interceptor:
bash
nest g interceptor catch-error-test --flat --no-spec
使用 catchError 处理抛出的异常:
typescript
import {
CallHandler,
ExecutionContext,
Injectable,
Logger,
NestInterceptor,
} from '@nestjs/common';
import { catchError, Observable, throwError } from 'rxjs';
@Injectable()
export class CatchErrorTestInterceptor implements NestInterceptor {
private readonly logger = new Logger(CatchErrorTestInterceptor.name);
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
// 使用 catchError 操作符来捕获异常
catchError((err) => {
this.logger.error(err.message, err.stack);
// 重新抛出错误,让它可以被后续的错误处理器捕获
return throwError(() => err);
}),
);
}
}
在 controller 使用下:
这个 500 错误,是内置的 exception filter 处理的:
nest 控制台会打印两次错误,报错信息都是 xxx。
一次是我们在 interceptor 里打印的,一次是 exception filter 打印的。
timeout
timeout 操作符允许为请求处理程序设置一个超时时间。
如果在指定的时间内没有收到响应,它将抛出一个 TimeoutError 异常。
可以结合 catchError 操作符来处理这个异常,并返回一个适当的超时响应。
创建 interceptor:
typescript
nest g interceptor timeout --flat --no-spec
添加如下逻辑:
typescript
import {
Injectable,
NestInterceptor,
ExecutionContext,
CallHandler,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { Observable, TimeoutError, throwError } from 'rxjs';
import { catchError, timeout } from 'rxjs/operators';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// 设置超时时间为3秒
const TIMEOUT = 3000;
return next.handle().pipe(
timeout(TIMEOUT), // 在3秒后如果还没有响应,则抛出TimeoutError
catchError((error) => {
if (error instanceof TimeoutError) {
// 如果捕获到超时错误,则抛出HttpException
return throwError(
() => new HttpException('请求超时', HttpStatus.REQUEST_TIMEOUT),
);
}
// 如果是其他类型的错误,则继续抛出
return throwError(() => error);
}),
);
}
}
这样 timeout 操作符会在 3s 没收到消息的时候抛一个错误。
在 controller 使用下:
浏览器访问,3s 后返回 408 响应:
Interceptor 和 Middleware 的区别
主要区别是 Interceptor 可以拿到调用的 controller 和 handler,而 middleware 不行。
typescript
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const controller = context.getClass(); // 获取当前调用的控制器
const handler = context.getHandler(); // 获取当前调用的处理器
// 执行下一个中间件或处理器
return next.handle().pipe(
tap(() => console.log('After...'))
);
}
}