RxJS 和 Interceptor 又是什么?

65 阅读2分钟

RxJS 是一个组织异步逻辑的库,可简化异步逻辑和回调的编写

image.png

Nest 的 interceptor 集成了 RxJS,可以用它来处理响应

新建项目看看

nest new interceptor-rxjs -p npm

新建

nest g interceptor aaa --flat --no-spec

记录接口时间


import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, tap } from 'rxjs';

@Injectable()
export class AaaInterceptor implements NestInterceptor {

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`Using... ${Date.now() - now}ms`)),
      );
  }
}

使用

image.png

访问http://localhost:3000/

image.png

这是 interceptor 最基本使用

使用下 RxJS operator

map

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

controller 中使用

image.png

image.png

tap

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 log log`, data);
    }))
  }
}

使用

image.png

image.png

catchError

controller 里很可能会抛出错误,这些错误会被 exception filter 处理,返回不同的响应,在那之前,可以在 interceptor 先处理下

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

更新下

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(err => {
      this.logger.error(err.message, err.stack)
      return throwError(() => err)
    }))
  }
}

使用

image.png

image.png

错误打印

image.png

还有一次错误打印

image.png

一次是在 interceptor 里打印的,一次是 exception filter 打印

timeout

接口如果长时间没返回,要给用户一个接口超时的响应,可以用 timeout operator

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

更新

import { CallHandler, ExecutionContext, Injectable, NestInterceptor, RequestTimeoutException } from '@nestjs/common';
import { catchError, Observable, throwError, timeout, TimeoutError } from 'rxjs';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(3000),
      catchError(err => {
        if(err instanceof TimeoutError) {
          console.log(err);
          return throwError(() => new RequestTimeoutException());
        }
        return throwError(() => err);
      })
    )
  }
}

会在 3s 没收到消息的时候抛一个 TimeoutError。

然后用 catchError 处理,如果是 TimeoutError,就返回 RequestTimeoutException,这个有内置的 exception filter 会处理成对应的响应格式。

其余错误就直接 throw Error 抛出去

使用

image.png

image.png

此处处理

image.png

可以换一个试试

image.png

image.png

再试试 全局的 interceptor

image.png

这种是手动 new 的,没法注入依赖

但很多情况下我们是需要全局 interceptor 的,而且还用到一些 provider,怎么办呢?

nest 提供了一个 token,用这个 token 在 AppModule 里声明的 interceptor,Nest 会把它作为全局 interceptor

image.png

在这个 interceptor 里注入了 appService

image.png

image.png

可以看到全局 interceptor 生效了,而且这个 hello world 就是注入的 appService 返回的

image.png