深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理

RxJS 初识

RxJS 是一个组织异步逻辑的库,它有很多 operator,可以极大的简化异步逻辑的编写。
它是由数据源(observable)产生数据,经过一系列 operator 的处理,最后传给接收者。
比如这样:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
调用 of 操作符创建一个 Observable,它发送了三个值 1、2、3。
使用 pipe 方法,将 map 操作符添加到 Observable 上,对每个输入值进行平方运算,得到一个新的 Observable,它发送了 1、4、9。
再次使用 pipe 方法,将 filter 操作符添加到 Observable 上,过滤掉所有偶数值,得到一个新的 Observable,它发送了 1、9。
最后,调用 subscribe 方法订阅这个 Observable,当它发送新值时,调用提供的回调函数输出结果。在这个例子中,回调函数输出了每个接收到的值。

继续看这段代码:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
调用 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,当它发送新值时,调用提供的回调函数输出结果。在这个例子中,回调函数输出了每个接收到的平均数结果。

再来看节流、防抖:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
可以在官网文档看到所有的 operator
如果异步逻辑复杂度高, RxJS 收益还是很高的。
Nest 的 interceptor 集成了 RxJS,可以用它来处理响应。

拦截器初识

创建一个测试项目:

nest new interceptor-test -p npm

进入目录执行:

nest g interceptor logging --flat --no-spec

记录下请求时间:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理

使用拦截器的方式:
方法级别:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
访问页面,控制台打印:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
全局:

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

控制器级别:

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:

nest g interceptor map-test --flat --no-spec

使用 map operator 对 controller 返回的数据做一些修改:

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 下启用:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
请求接口:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理

tap

tap 操作符允许在不修改数据流的情况下执行副作用操作,比如记录日志或更新缓存。
这对于在请求处理过程中添加额外的日志记录或调试信息非常有用。
再生成个 interceptor

nest g interceptor tap-test --flat --no-spec

使用 tap operator 来添加一些日志、缓存等逻辑:

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 返回响应的时候记录一些东西。
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理

catchError

catchError 操作符允许处理在请求处理程序中抛出的异常。
可以先在拦截器中捕获这些异常,并执行一些额外的逻辑,比如记录错误日志或返回自定义的错误响应。
生成 interceptor:

nest g interceptor catch-error-test --flat --no-spec

使用 catchError 处理抛出的异常:

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 使用下:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
这个 500 错误,是内置的 exception filter 处理的:
nest 控制台会打印两次错误,报错信息都是 xxx。
一次是我们在 interceptor 里打印的,一次是 exception filter 打印的。

timeout

timeout 操作符允许为请求处理程序设置一个超时时间。
如果在指定的时间内没有收到响应,它将抛出一个 TimeoutError 异常。
可以结合 catchError 操作符来处理这个异常,并返回一个适当的超时响应。
创建 interceptor:

nest g interceptor timeout --flat --no-spec

添加如下逻辑:

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 使用下:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理
浏览器访问,3s 后返回 408 响应:
深入浅出 RxJS 与 Nest 拦截器:异步逻辑的优雅处理

Interceptor 和 Middleware 的区别

主要区别是 Interceptor 可以拿到调用的 controller 和 handler,而 middleware 不行。

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

原文链接:https://juejin.cn/post/7340902427615117339 作者:云牧牧

(0)
上一篇 2024年3月1日 上午11:08
下一篇 2024年3月1日 下午4:00

相关推荐

发表回复

登录后才能评论