装饰器
装饰器
¥Decorators
装饰器 API 自定义核心 Fastify 对象,例如服务器实例以及 HTTP 请求生命周期中使用的任何请求和回复对象。它可以将任何类型的属性附加到核心对象,例如函数、普通对象或原生类型。
¥The decorators API customizes core Fastify objects, such as the server instance and any request and reply objects used during the HTTP request lifecycle. It can attach any type of property to core objects, e.g., functions, plain objects, or native types.
该 API 是同步的。异步定义装饰可能会导致 Fastify 实例在装饰完成之前启动。要注册异步修饰,请将 register
API 与 fastify-plugin
结合使用。有关更多详细信息,请参阅 插件 文档。
¥This API is synchronous. Defining a decoration asynchronously could result in
the Fastify instance booting before the decoration completes. To register an
asynchronous decoration, use the register
API with fastify-plugin
. See the
Plugins documentation for more details.
使用此 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 可以避免这种去优化:
¥The above example mutates the request object after instantiation, causing the JavaScript engine to deoptimize access. Using the decoration API avoids this deoptimization:
// 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 引擎基础知识:形状和内联缓存。
¥Keep the initial shape of a decorated field close to its future dynamic value.
Initialize a decorator as ''
for strings and null
for objects or functions.
This works only with value types; reference types will throw an error during
Fastify startup. See decorateRequest and
JavaScript engine fundamentals: Shapes
and Inline Caches
for more information.
用法
¥Usage
decorate(name, value, [dependencies])
此方法自定义 Fastify server 实例。
¥This method customizes the Fastify server instance.
例如,要将新方法附加到服务器实例:
¥For example, to attach a new method to the server instance:
fastify.decorate('utility', function () {
// Something very useful
})
非函数值也可以附加到服务器实例:
¥Non-function values can also be attached to the server instance:
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 contains the names of other
decorators. In the following example, the "utility" decorator depends on the
"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
})
使用箭头函数会破坏 this
与 FastifyInstance
的绑定。
¥Using an arrow function breaks the binding of this
to
the FastifyInstance
.
如果依赖不满足,decorate
方法将抛出异常。依赖检查发生在服务器实例启动之前,而不是在运行时。
¥If a dependency is not satisfied, the decorate
method throws an exception.
The dependency check occurs before the server instance boots, not during
runtime.
decorateReply(name, value, [dependencies])
此 API 向核心 Reply
对象添加了新方法/属性:
¥This API adds new methods/properties to the core Reply
object:
fastify.decorateReply('utility', function () {
// Something very useful
})
使用箭头函数将破坏 this
与 Fastify Reply
实例的绑定。
¥Using an arrow function will break the binding of this
to the Fastify
Reply
instance.
如果与引用类型一起使用,使用 decorateReply
将引发错误:
¥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 object reference would be shared with all requests, and any mutation will impact all requests, potentially creating security vulnerabilities or memory leaks. Fastify blocks this.
为了实现跨请求的正确封装,请为 'onRequest'
钩 中的每个传入请求配置一个新值。
¥To achieve proper encapsulation across requests configure a new value for each
incoming request in the 'onRequest'
hook.
const fp = require('fastify-plugin')
async function myPlugin (app) {
app.decorateReply('foo')
app.addHook('onRequest', async (req, reply) => {
reply.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 with decorateReply
, this API adds new methods/properties
to the core Request
object:
fastify.decorateRequest('utility', function () {
// something very useful
})
使用箭头函数将破坏 this
与 Fastify Request
实例的绑定。
¥Using an arrow function will break the binding of this
to the Fastify
Request
instance.
如果与引用类型一起使用,使用 decorateRequest
将引发错误:
¥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 object reference would be shared with all requests, and any mutation will impact all requests, potentially creating security vulnerabilities or memory leaks. Fastify blocks this.
为了实现跨请求的正确封装,请为 '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 more logic can be added 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
在同一个封装上下文中多次定义具有相同名称的装饰器(使用 decorate
、decorateRequest
或 decorateReply
)将引发异常。例如,以下内容将抛出:
¥Defining a decorator (using decorate
, decorateRequest
, or decorateReply
)
with the same name more than once in the same encapsulated context will
throw an exception. For 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
函数的特殊 "getter/setter" 对象。这允许通过装饰器定义属性,例如:
¥Decorators accept special "getter/setter" objects with getter
and optional
setter
functions. 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'
getDecorator\<T\>
API
Fastify 的 getDecorator<T>
API 会从 Fastify 实例、Request
或 Reply
中检索现有的装饰器。如果装饰器未定义,则会抛出 FST_ERR_DEC_UNDECLARED
错误。
¥Fastify's getDecorator<T>
API retrieves an existing decorator from the
Fastify instance, Request
, or Reply
. If the decorator is not defined, an
FST_ERR_DEC_UNDECLARED
error is thrown.
用例
¥Use cases
早期插件依赖验证
¥Early Plugin Dependency Validation
Fastify 实例上的 getDecorator<T>
会在注册时验证所需的装饰器是否可用。
¥getDecorator<T>
on Fastify instance verifies that required decorators are
available at registration time.
例如:
¥For example:
fastify.register(async function (fastify) {
const usersRepository = fastify.getDecorator('usersRepository')
fastify.get('/users', async function (request, reply) {
// We are sure `usersRepository` exists at runtime
return usersRepository.findAll()
})
})
处理缺失的装饰器
¥Handling Missing Decorators
如果未声明装饰器,直接访问装饰器可能会导致意外行为:
¥Directly accessing a decorator may lead to unexpected behavior if it is not declared:
const user = request.user;
if (user && user.isAdmin) {
// Execute admin tasks.
}
如果 request.user
不存在,则 user
将被设置为 undefined
。这使得用户是否未经身份验证或装饰器是否缺失变得难以判断。
¥If request.user
doesn't exist, then user
will be set to undefined
.
This makes it unclear whether the user is unauthenticated or the decorator is missing.
使用 getDecorator
可以增强运行时安全性:
¥Using getDecorator
enforces runtime safety:
// If the decorator is missing, an explicit `FST_ERR_DEC_UNDECLARED`
// error is thrown immediately.
const user = request.getDecorator('user');
if (user && user.isAdmin) {
// Execute admin tasks.
}
模块扩充的替代方案
¥Alternative to Module Augmentation
装饰器通常通过模块扩展进行类型化:
¥Decorators are typically typed via module augmentation:
declare module 'fastify' {
interface FastifyInstance {
usersRepository: IUsersRepository
}
interface FastifyRequest {
session: ISession
}
interface FastifyReply {
sendSuccess: SendSuccessFn
}
}
此方法会全局修改 Fastify 实例,这可能会导致多服务器设置或插件封装中的冲突和行为不一致。
¥This approach modifies the Fastify instance globally, which may lead to conflicts and inconsistent behavior in multi-server setups or with plugin encapsulation.
使用 getDecorator<T>
可以限制类型范围:
¥Using getDecorator<T>
allows to limit types scope:
serverOne.register(async function (fastify) {
const usersRepository = fastify.getDecorator<PostgreUsersRepository>(
'usersRepository'
)
fastify.decorateRequest('session', null)
fastify.addHook('onRequest', async (req, reply) => {
// Yes, the request object has a setDecorator method.
// More information will be provided soon.
req.setDecorator('session', { user: 'Jean' })
})
fastify.get('/me', (request, reply) => {
const session = request.getDecorator<ISession>('session')
reply.send(session)
})
})
serverTwo.register(async function (fastify) {
const usersRepository = fastify.getDecorator<SqlLiteUsersRepository>(
'usersRepository'
)
fastify.decorateReply('sendSuccess', function (data) {
return this.send({ success: true })
})
fastify.get('/success', async (request, reply) => {
const sendSuccess = reply.getDecorator<SendSuccessFn>('sendSuccess')
await sendSuccess()
})
})
绑定函数推断
¥Bound functions inference
为了节省时间,通常推断函数类型而不是手动编写:
¥To save time, it's common to infer function types instead of writing them manually:
function sendSuccess (this: FastifyReply) {
return this.send({ success: true })
}
export type SendSuccess = typeof sendSuccess
但是,getDecorator
返回的函数已经绑定了 this
上下文,这意味着 this
参数将从函数签名中消失。
¥However, getDecorator
returns functions with the this
context already bound, meaning the this
parameter disappears
from the function signature.
为了正确输入,你应该使用 OmitThisParameter
实用程序:
¥To correctly type it, you should use OmitThisParameter
utility:
function sendSuccess (this: FastifyReply) {
return this.send({ success: true })
}
type BoundSendSuccess = OmitThisParameter<typeof sendSuccess>
fastify.decorateReply('sendSuccess', sendSuccess)
fastify.get('/success', async (request, reply) => {
const sendSuccess = reply.getDecorator<BoundSendSuccess>('sendSuccess')
await sendSuccess()
})
Request.setDecorator\<T\>
方法
¥Request.setDecorator<T>
Method
setDecorator<T>
方法提供了一种安全便捷的方法来更新 Request
装饰器的值。如果装饰器不存在,则会抛出 FST_ERR_DEC_UNDECLARED
错误。
¥The setDecorator<T>
method provides a safe and convenient way to
update the value of a Request
decorator.\ If the decorator does not exist, a FST_ERR_DEC_UNDECLARED
error
is thrown.
用例
¥Use Cases
运行时安全
¥Runtime Safety
设置 Request
装饰器的典型方法如下:
¥A typical way to set a Request
decorator looks like this:
fastify.decorateRequest('user', '')
fastify.addHook('preHandler', async (req, reply) => {
req.user = 'Bob Dylan'
})
但是,除非你事先手动检查,否则无法保证装饰器确实存在。此外,拼写错误也很常见,例如 account
、acount
或 accout
。
¥However, there is no guarantee that the decorator actually exists
unless you manually check beforehand.\ Additionally, typos are common, e.g. account
, acount
, or accout
.
使用 setDecorator
,你可以始终确保装饰器存在:
¥By using setDecorator
, you are always sure that the decorator exists:
fastify.decorateRequest('user', '')
fastify.addHook('preHandler', async (req, reply) => {
// Throws FST_ERR_DEC_UNDECLARED if the decorator does not exist
req.setDecorator('user-with-typo', 'Bob Dylan')
})
类型安全
¥Type Safety
如果 FastifyRequest
接口未声明装饰器,通常需要使用类型断言:
¥If the FastifyRequest
interface does not declare the decorator, you
would typically need to use type assertions:
fastify.addHook('preHandler', async (req, reply) => {
(req as typeof req & { user: string }).user = 'Bob Dylan'
})
setDecorator<T>
方法无需显式类型断言,同时确保类型安全:
¥The setDecorator<T>
method eliminates the need for explicit type
assertions while allowing type safety:
fastify.addHook('preHandler', async (req, reply) => {
req.setDecorator<string>('user', 'Bob Dylan')
})