Skip to main content

装饰器

装饰器

¥Decorators

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

¥The decorators API allows customization of the core Fastify objects, such as the server instance itself and any request and reply objects used during the HTTP request lifecycle. The decorators API can be used to attach any type of property to the core objects, e.g. functions, plain objects, or native types.

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

¥This API is synchronous. Attempting to define a decoration asynchronously could result in the Fastify instance booting before the decoration completes its initialization. To avoid this issue, and register an asynchronous decoration, the register API, in combination with fastify-plugin, must be used instead. To learn more, see the Plugins documentation.

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

¥Decorating core objects with this API allows the underlying JavaScript engine to optimize the handling of server, request, and reply objects. This is accomplished by defining the shape of all such object instances before they are instantiated and used. As an example, the following is not recommended because it will change the shape of objects during their lifecycle:

// 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,可以避免这种去优化:

¥Since the above example mutates the request object after it has already been instantiated, the JavaScript engine must deoptimize access to the request object. By using the decoration API this deoptimization is avoided:

// 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

¥Note that it is important to keep the initial shape of a decorated field as close as possible to the value intended to be set dynamically in the future. Initialize a decorator as a '' if the intended value is a string, and as null if it will be an object or a function.

请记住,此示例仅适用于值类型,因为在 fastify 启动期间将抛出引用类型并出错。参见 decorateRequest

¥Remember this example works only with value types as reference types will thrown and error during the fastify startup. See decorateRequest.

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

¥See JavaScript engine fundamentals: Shapes and Inline Caches for more information on this topic.

用法

¥Usage

decorate(name, value, [dependencies])

此方法用于自定义 Fastify server 实例。

¥This method is used to customize the Fastify server instance.

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

¥For example, to attach a new method to the server instance:

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

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

¥As mentioned above, non-function values can be attached to the server instance as:

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

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

¥To access decorated properties, use the name provided to the decoration API:

fastify.utility()

console.log(fastify.conf.db)

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

¥The decorated Fastify server is bound to this in route handlers:

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" 装饰器:

¥The dependencies parameter is an optional list of decorators that the decorator being defined relies upon. This list is simply a list of string names of other decorators. In the following example, the "utility" decorator depends upon "greet" and "hi" decorators:

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 的绑定。

¥Note: using an arrow function will break the binding of this to the FastifyInstance.

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

¥If a dependency is not satisfied, the decorate method will throw an exception. The dependency check is performed before the server instance is booted. Thus, it cannot occur during runtime.

decorateReply(name, value, [dependencies])

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

¥As the name suggests, this API is used to add new methods/properties to the core Reply object:

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

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

¥Note: using an arrow function will break the binding of this to the Fastify Reply instance.

注意:如果与引用类型一起使用,使用 decorateReply 将引发错误:

¥Note: using decorateReply will throw and error if used with a reference type:

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

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

¥In this example, the reference of the object would be shared with all the requests and any mutation will impact all requests, potentially creating security vulnerabilities or memory leaks, so Fastify blocks it.

为了实现跨请求的正确封装,请为 'onRequest' 中的每个传入请求配置一个新值。示例:

¥To achieve proper encapsulation across requests configure a new value for each incoming request in the 'onRequest' hook. Example:

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)

请参阅 decorate 了解 dependencies 参数的信息。

¥See decorate for information about the dependencies parameter.

decorateRequest(name, value, [dependencies])

与上面的 decorateReply 一样,此 API 用于向核心 Request 对象添加新方法/属性:

¥As above with decorateReply, this API is used add new methods/properties to the core Request object:

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

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

¥Note: using an arrow function will break the binding of this to the Fastify Request instance.

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

¥Note: using decorateRequest will emit an error if used with a reference type:

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

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

¥In this example, the reference of the object would be shared with all the requests and any mutation will impact all requests, potentially creating security vulnerabilities or memory leaks, so Fastify blocks it.

为了实现跨请求的正确封装,请为 'onRequest' 中的每个传入请求配置一个新值。

¥To achieve proper encapsulation across requests configure a new value for each incoming request in the 'onRequest' hook.

示例:

¥Example:

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 钩子添加更多逻辑。

¥The hook solution is more flexible and allows for more complex initialization because you can add more logic to the onRequest hook.

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

¥Another approach is to use the getter/setter pattern, but it requires 2 decorators:

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 属性对于每个请求始终是唯一的。

¥This ensures that the user property is always unique for each request.

请参阅 decorate 了解 dependencies 参数的信息。

¥See decorate for information about the dependencies parameter.

hasDecorator(name)

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

¥Used to check for the existence of a server instance decoration:

fastify.hasDecorator('utility')

hasRequestDecorator

用于检查 Request 装饰是否存在:

¥Used to check for the existence of a Request decoration:

fastify.hasRequestDecorator('utility')

hasReplyDecorator

用于检查 Reply 装饰是否存在:

¥Used to check for the existence of a Reply decoration:

fastify.hasReplyDecorator('utility')

装饰器和封装

¥Decorators and Encapsulation

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

¥Defining a decorator (using decorate, decorateRequest, or decorateReply) with the same name more than once in the same encapsulated context will throw an exception.

例如,以下内容将抛出:

¥As an example, the following will throw:

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

但这不会:

¥But this will not:

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

获取器和设置器

¥Getters and Setters

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

¥Decorators accept special "getter/setter" objects. These objects have functions named getter and setter (though the setter function is optional). This allows defining properties via decorators, for example:

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

将在 Fastify 实例上定义 foo 属性:

¥Will define the foo property on the Fastify instance:

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