Skip to content

请求生命周期

Nest 应用在处理请求并生成响应时,会经历一个我们称为请求生命周期的顺序流程。由于中间件、管道、守卫和拦截器都会参与其中,尤其当全局级、控制器级和路由级组件同时存在时,往往不容易判断某段代码究竟是在生命周期的哪个阶段执行的。总体来说,一个请求会依次经过中间件、守卫、拦截器、管道,然后在返回路径上再次经过拦截器,最终生成响应。

中间件

中间件按特定顺序执行。首先,Nest 会运行全局绑定的中间件(例如通过 app.use() 绑定的中间件),然后再运行按路径匹配的模块级中间件。中间件会按照它们被绑定的顺序依次执行,这一点与 Express 中间件的行为一致。如果中间件分布在不同模块中,则绑定在根模块上的中间件会先执行,随后再按模块在 imports 数组中的顺序依次执行。

守卫

守卫的执行顺序是:先执行全局守卫,再执行控制器守卫,最后执行路由守卫。与中间件类似,守卫也是按绑定顺序执行。例如:

typescript
@UseGuards(Guard1, Guard2)
@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @UseGuards(Guard3)
  @Get()
  getCats(): Cats[] {
    return this.catsService.getCats();
  }
}

Guard1 会先于 Guard2 执行,而它们二者又都会先于 Guard3 执行。

提示

所谓“全局绑定”与“控制器级 / 本地绑定”的区别,本质上在于组件是在哪里被绑定的。如果你使用的是 app.useGlobalGuards(),或者通过模块提供该组件,那么它就是全局绑定。否则,如果装饰器作用在控制器类上,它就是控制器级绑定;如果装饰器作用在某个路由处理方法上,它就是路由级绑定。

拦截器

拦截器在大部分情况下遵循与守卫相同的模式,但有一个关键区别:由于拦截器返回的是 RxJS Observable,因此这些 Observable 会以“后进先出”的方式解析。也就是说,请求进入时会按全局、控制器、路由的顺序依次经过拦截器;但响应返回时(即从控制器方法处理器返回之后),则会按路由、控制器、全局的顺序反向经过拦截器。

另外,管道、控制器或服务中抛出的任何错误,都可以在拦截器的 catchError 操作符中被读取到。

管道

管道遵循标准的“全局 -> 控制器 -> 路由”绑定顺序,并且对于 @UsePipes() 传入的多个管道,同样按先进先出的顺序执行。不过,在路由参数级别,如果你有多个参数管道,它们会按照“从最后一个带管道的参数到第一个参数”的顺序执行。这个规则同样会影响路由级和控制器级管道。比如下面这个控制器:

typescript
@UsePipes(GeneralValidationPipe)
@Controller('cats')
export class CatsController {
  constructor(private catsService: CatsService) {}

  @UsePipes(RouteSpecificPipe)
  @Patch(':id')
  updateCat(
    @Body() body: UpdateCatDTO,
    @Param() params: UpdateCatParams,
    @Query() query: UpdateCatQuery,
  ) {
    return this.catsService.updateCat(body, params, query);
  }
}

那么 GeneralValidationPipe 会先作用于 query,再作用于 params,最后作用于 body,然后才轮到 RouteSpecificPipe,它也会按照同样的参数顺序执行。如果还定义了参数级别的管道,那么它们会在控制器级和路由级管道之后执行,并且同样遵循“从最后一个参数到第一个参数”的顺序。

过滤器

过滤器是唯一一个不是从全局优先开始解析的组件。相反,它总是从最底层开始解析,也就是说先执行路由级过滤器,再执行控制器级过滤器,最后才是全局过滤器。

需要注意的是,异常不会在过滤器之间继续传递。如果一个路由级过滤器已经捕获了该异常,那么控制器级或全局过滤器就无法再次捕获同一个异常。若你确实需要实现类似“逐层处理”的效果,唯一办法是让过滤器之间通过继承来复用行为。

提示

过滤器只会在请求过程中出现未捕获异常时执行。像 try/catch 这样已经被捕获的异常,不会触发异常过滤器。一旦遇到未捕获异常,请求生命周期中剩余的其他步骤都会被忽略,流程会直接跳转到过滤器。

总结

总体来说,一个请求的生命周期如下:

  1. 进入请求
  2. 中间件
  3. 全局中间件
  4. 模块级中间件
  5. 守卫
  6. 全局守卫
  7. 控制器守卫
  8. 路由守卫
  9. 拦截器(控制器前)
  10. 全局拦截器
  11. 控制器拦截器
  12. 路由拦截器
  13. 管道
  14. 全局管道
  15. 控制器管道
  16. 路由管道
  17. 路由参数管道
  18. 控制器(方法处理器)
  19. 服务(如果存在)
  20. 拦截器(请求后)
  21. 路由拦截器
  22. 控制器拦截器
  23. 全局拦截器
  24. 异常过滤器
  25. 路由级过滤器
  26. 控制器级过滤器
  27. 全局过滤器
  28. 服务器响应

基于 NestJS 官方文档翻译