跳至主要内容
版本:最新版本 (v5.0.x)

装饰器

装饰器

装饰器 API 允许自定义 Fastify 核心对象,例如服务器实例本身以及 HTTP 请求生命周期中使用的任何请求和响应对象。装饰器 API 可用于将任何类型的属性附加到核心对象,例如函数、普通对象或原生类型。

此 API 是同步的。尝试异步定义装饰可能会导致 Fastify 实例在装饰完成初始化之前启动。为了避免此问题并注册异步装饰,必须改为使用register API 并结合fastify-plugin。要了解更多信息,请参阅插件文档。

使用此 API 装饰核心对象允许底层 JavaScript 引擎优化服务器、请求和响应对象的处理。这是通过在实例化和使用这些对象实例之前定义所有此类对象实例的形状来实现的。例如,以下操作不建议使用,因为它会更改对象在其生命周期中的形状

// Bad example! Continue reading.

// Attach a user property to the incoming request before the request
// handler is invoked.
fastify.addHook('preHandler', function (req, reply, done) {
req.user = 'Bob Dylan'
done()
})

// Use the attached user property in the request handler.
fastify.get('/', function (req, reply) {
reply.send(`Hello, ${req.user}`)
})

由于以上示例在请求对象已实例化后对其进行了修改,因此 JavaScript 引擎必须对访问请求对象进行降级优化。通过使用装饰器 API 可以避免这种降级优化。

// Decorate request with a 'user' property
fastify.decorateRequest('user', '')

// Update our property
fastify.addHook('preHandler', (req, reply, done) => {
req.user = 'Bob Dylan'
done()
})
// And finally access it
fastify.get('/', (req, reply) => {
reply.send(`Hello, ${req.user}!`)
})

请注意,保持装饰字段的初始形状尽可能接近将来打算动态设置的值非常重要。如果目标值为字符串,则将装饰器初始化为'',如果目标值为对象或函数,则初始化为null

请记住,此示例仅适用于值类型,因为引用类型会在 Fastify 启动期间抛出错误。请参阅decorateRequest

有关此主题的更多信息,请参阅JavaScript 引擎基础知识:形状和内联缓存

用法

decorate(name, value, [dependencies])

此方法用于自定义 Fastify 服务器实例。

例如,要将新方法附加到服务器实例

fastify.decorate('utility', function () {
// Something very useful
})

如上所述,非函数值可以附加到服务器实例,例如

fastify.decorate('conf', {
db: 'some.db',
port: 3000
})

要访问装饰的属性,请使用提供给装饰器 API 的名称

fastify.utility()

console.log(fastify.conf.db)

装饰的Fastify 服务器路由处理程序中绑定到this

fastify.decorate('db', new DbConnection())

fastify.get('/', async function (request, reply) {
// using return
return { hello: await this.db.query('world') }

// or
// using reply.send()
reply.send({ hello: await this.db.query('world') })
await reply
})

dependencies参数是正在定义的装饰器所依赖的装饰器的可选列表。此列表只是一个其他装饰器字符串名称的列表。在以下示例中,“utility”装饰器依赖于“greet”和“hi”装饰器

async function greetDecorator (fastify, opts) {
fastify.decorate('greet', () => {
return 'greet message'
})
}

async function hiDecorator (fastify, opts) {
fastify.decorate('hi', () => {
return 'hi message'
})
}

async function utilityDecorator (fastify, opts) {
fastify.decorate('utility', () => {
return `${fastify.greet()} | ${fastify.hi()}`
})
}

fastify.register(fastifyPlugin(greetDecorator, { name: 'greet' }))
fastify.register(fastifyPlugin(hiDecorator, { name: 'hi' }))
fastify.register(fastifyPlugin(utilityDecorator, { dependencies: ['greet', 'hi'] }))

fastify.get('/', function (req, reply) {
// Response: {"hello":"greet message | hi message"}
reply.send({ hello: fastify.utility() })
})

fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err
})

注意:使用箭头函数将破坏thisFastifyInstance的绑定。

如果依赖项不满足,则decorate方法将抛出异常。依赖项检查在服务器实例启动之前执行。因此,它不能在运行时发生。

decorateReply(name, value, [dependencies])

顾名思义,此 API 用于向核心Reply对象添加新方法/属性

fastify.decorateReply('utility', function () {
// Something very useful
})

注意:使用箭头函数将破坏this到 Fastify Reply实例的绑定。

注意:如果将decorateReply与引用类型一起使用,则会抛出错误

// Don't do this
fastify.decorateReply('foo', { bar: 'fizz'})

在此示例中,对象的引用将与所有请求共享,并且**任何修改都将影响所有请求,这可能会造成安全漏洞或内存泄漏**,因此 Fastify 会阻止它。

要在请求之间实现正确的封装,请在'onRequest'钩子中为每个传入请求配置一个新值。示例

const fp = require('fastify-plugin')

async function myPlugin (app) {
app.decorateRequest('foo')
app.addHook('onRequest', async (req, reply) => {
req.foo = { bar: 42 }
})
}

module.exports = fp(myPlugin)

有关dependencies参数的信息,请参阅decorate

decorateRequest(name, value, [dependencies])

decorateReply类似,此 API 用于向核心Request对象添加新方法/属性

fastify.decorateRequest('utility', function () {
// something very useful
})

注意:使用箭头函数将破坏this到 Fastify Request实例的绑定。

注意:如果将decorateRequest与引用类型一起使用,则会发出错误

// Don't do this
fastify.decorateRequest('foo', { bar: 'fizz'})

在此示例中,对象的引用将与所有请求共享,并且**任何修改都将影响所有请求,这可能会造成安全漏洞或内存泄漏**,因此 Fastify 会阻止它。

要在请求之间实现正确的封装,请在'onRequest'钩子中为每个传入请求配置一个新值。

示例

const fp = require('fastify-plugin')

async function myPlugin (app) {
app.decorateRequest('foo')
app.addHook('onRequest', async (req, reply) => {
req.foo = { bar: 42 }
})
}

module.exports = fp(myPlugin)

钩子解决方案更灵活,并且允许更复杂的初始化,因为您可以向onRequest钩子添加更多逻辑。

另一种方法是使用 getter/setter 模式,但这需要 2 个装饰器

fastify.decorateRequest('my_decorator_holder') // define the holder
fastify.decorateRequest('user', {
getter () {
this.my_decorator_holder ??= {} // initialize the holder
return this.my_decorator_holder
}
})

fastify.get('/', async function (req, reply) {
req.user.access = 'granted'
// other code
})

这确保了user属性对于每个请求都是唯一的。

有关dependencies参数的信息,请参阅decorate

hasDecorator(name)

用于检查服务器实例装饰器是否存在

fastify.hasDecorator('utility')

hasRequestDecorator

用于检查请求装饰器是否存在

fastify.hasRequestDecorator('utility')

hasReplyDecorator

用于检查响应装饰器是否存在

fastify.hasReplyDecorator('utility')

装饰器和封装

在同一封装上下文中多次使用相同的名称定义装饰器(使用decoratedecorateRequestdecorateReply)将抛出异常。

例如,以下操作将抛出异常

const server = require('fastify')()

server.decorateReply('view', function (template, args) {
// Amazing view rendering engine
})

server.get('/', (req, reply) => {
reply.view('/index.html', { hello: 'world' })
})

// Somewhere else in our codebase, we define another
// view decorator. This throws.
server.decorateReply('view', function (template, args) {
// Another rendering engine
})

server.listen({ port: 3000 })

但以下操作不会

const server = require('fastify')()

server.decorateReply('view', function (template, args) {
// Amazing view rendering engine.
})

server.register(async function (server, opts) {
// We add a view decorator to the current encapsulated
// plugin. This will not throw as outside of this encapsulated
// plugin view is the old one, while inside it is the new one.
server.decorateReply('view', function (template, args) {
// Another rendering engine
})

server.get('/', (req, reply) => {
reply.view('/index.page', { hello: 'world' })
})
}, { prefix: '/bar' })

server.listen({ port: 3000 })

Getter 和 Setter

装饰器接受特殊的“getter/setter”对象。这些对象具有名为gettersetter的函数(尽管setter函数是可选的)。这允许通过装饰器定义属性,例如

fastify.decorate('foo', {
getter () {
return 'a getter'
}
})

将在 Fastify 实例上定义foo属性

console.log(fastify.foo) // 'a getter'