四、响应和Next

在上一节,我们说了请求参数的装饰器,这一节我们看下响应@Res@Next

前置知识

与请求类似,响应也有两个相同功能的装饰器@Response@ResResResponse的别名。

两者直接公开底层原生平台的response对象接口(当然在我们这里就是express.Response,但是Nest本身还支持express之外的其他库作为底层)。如果注入了Res/Response,那就需要自己管理响应了(比如res.send(...)),否则该服务会被挂起,不会响应。

当然这不是绝对的,如果你只是想注入Response用来设置一些headers/cookie等内容,并不想自己来负责响应,那么可以通过@Response({passthrough:true})来实现,在方法中正常return要返回的内容即可

为什么会有@Res@Response的别名,@Req@Request的别名呢?

因为@Request装饰的参数会传递expressRequest类型,@Response同理,所以如果这样写:

1
2
3
4
import { Request } from '@nestjs/common'
import type { Request } from 'express'
@Get()
getHello(@Request request: Request) {}

他们都叫Request,名字会重复,所以在用的时候要么得起别名import type { Request as ExpressRequest } from 'express',要么直接只用@Req来替代@Request会是一个好办法。

@Res

我们先添加了两个新的路由,分别是自己控制响应和自动返回响应内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
// users.controller.ts
@Post('res')
handleRes(@Res() res: Response) {
console.log('res:', res)
// return 'handle response' // 无法通过return返回,需要自己控制响应
res.send('success')
}

@Post('res-auto')
handleResAuto(@Res({passthrough: true}) res: Response) {
console.log('res:', res)
return 'handle response' // 可以通过return返回
}

第一步我们先实现@Res/@Response两个装饰器,跟其他的并无什么差异:

1
2
3
4
// @nestjs/common/param.decorator.ts
// @Response 和 @Res是一样的
export const Response = createParamDecorator('Response')
export const Res = createParamDecorator('Res')

NestApplication中我们依然要添加一个新的case,这里返回express的原生Response对象就好了:

1
2
3
4
5
// @nestjs/core/nest-application.ts

case 'Response':
case 'Res':
return res

在之前我们的代码中,我们在注册路由时,在处理程序中执行对应方法获取到响应值并将其返回到了客户端,但是现在我们需要判断是否要自动将响应值返回,所以我们在NestApplication中加了一个新的方法用于判断

1
2
3
4
5
6
7
8
9
10
11
// @nestjs/core/nest-application.ts

// 判断是否需要自动返回Response
private ifAutoResponse(controller: any, methodName: string) {
const metadata = Reflect.getMetadata(`params`, controller, methodName) || []
const param = metadata.find(param => param && (param.decorator === 'Response' || param.decorator === 'Res'))
// 如果未注入Res/Response,则自动返回
if (!param) return true
// 如果注入了Response且传递了passthrough=true则自动返回,否则不自动返回
return param.data && param.data.passthrough
}

NestApplication中的init方法里,我们在注册路由的时候就需要做一些判断了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// @nestjs/core/nest-application.ts
// 注册路由 - express上通过app.get('/path', () => {})来注册路由
this.app[httpMethod.toLowerCase()](routePath, (req: Request, res: Response, next: NextFunction) => {
// 解析参数
const args = this.resolveParams(controller, methodName, req, res, next)
// 交由对应方法来处理请求
const result = method.call(controller, ...args)
// 如果注入了@Response,且没有传递@Response({passthrough=true}),则需要自己控制响应, Nestjs不会返回客户端任何内容
// 所以需要判断是否需要将结果自动返回给客户端
const autoResponse = this.ifAutoResponse(controller, methodName)
if (autoResponse) {
res.send(result)
}
})

@Next

说完Response,还要提到一个就是@Next,我们知道路由处理程序有三个参数req, res, next,前边的请求和响应已经说完了,这里要说一下next,在expressnextkoa中的概念并不相同,这里并不是一个洋葱模型,而是如果你不执行next就不会执行后边的中间件了,所以当注入了@Next,那么也需要堵塞程序的运行

添加一个新的路由:

1
2
3
4
5
// users.controller.ts
@Get('next')
next(@Next() next) {
next()
}

定义@Next装饰器

1
2
3
// @nestjs/common/param.decorator.ts
// @Next
export const Next = createParamDecorator('Next')

NestApplication中添加一个新的Case,就是传递next

1
2
3
// @nestjs/core/nest-application.ts
case 'Next':
return next

在我们的判断是否自动返回的方法中,就需要添加一个新的判断条件了,ifAutoResponse方法修改为:

1
2
3
4
5
6
7
8
9
10
11
12
// @nestjs/core/nest-application.ts
// 判断是否需要自动返回Response
private ifAutoResponse(controller: any, methodName: string) {
const metadata = Reflect.getMetadata(`params`, controller, methodName) || []
const param = metadata.find(param => param && (param.decorator === 'Response' || param.decorator === 'Res' || param.decorator === 'Next'))
// 如果未注入Res/Response/Next,则自动返回
if (!param) return true
// 如果注入了@Next,则不会自动返回了
if (param.decorator === 'Next') return false
// 如果注入了Response且传递了passthrough=true则自动返回,否则不自动返回
return param.data && param.data.passthrough
}

这样就可以正常运行了。

到此,我们对于Response的处理就结束了,主要还是在NestApplication中的一些修改。

作者

胡兆磊

发布于

2024-12-21

更新于

2025-04-21

许可协议