我一直在开发一个lambda项目,我们正在使用lambda-api包。然后我定义了一些装饰称为Get
和Post
映射路由到lambda api对象。使用这些装饰器,我定义了一个名为ProductApi的类来保存可以使用这些装饰器配置并传递路由路径的方法。
问题是,当我有一个类,如ProductApi
构造函数永远不会被调用,如果我想添加一些依赖关系(如服务或存储库),它将永远不会被定义。在这个例子中,/health
路由工作得很好,因为它不使用对象实例中的任何东西,但是其他路由不使用。
我如何确保构造函数将被调用并定义服务实例?
const api = createAPI();
function Get(path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
api.get(path, descriptor.value.bind(target));
};
}
function Post(path: string) {
return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
api.post(path, descriptor.value.bind(target));
};
}
class ProductApi {
private someValue: string;
constructor(private readonly productService: IProductService = new ProductService()) {
// this scope does not execute
this.someValue = "some value";
}
@Get('/health')
async healthCheckr(req: Request, res: Response) {
console.log(`Executing -- GET /health`);
// this.someValue does not exists here
return res.status(200).json({ ok: true });
}
@Get('/products')
async getProducts(req: Request, res: Response) {
console.log(`Executing -- GET /products`);
const data = this.productService.getProductsFromService(); // error: Cannot read properties of undefined (reading 'getProductsFromService')
return res.status(200).json(data);
}
@Post('/products')
async postProducts(req: Request, res: Response) {
console.log(`Executing -- POST /products`);
const product = this.productService.saveProduct('Drums', 1200); // erro: Cannot read properties of undefined (reading 'saveProduct')
return res.status(201).json(product);
}
}
export const lambdaHandler = async (event: APIGatewayProxyEvent, context: Context): Promise<APIGatewayProxyResult> => {
console.log('SCOPE lambda');
return await api.run(event, context);
};
注意:我不想使用框架,我只想要一种在lambda api实例上配置路由的简单方法。
与c#不同,JS中有"method"只是一个固定在对象上的函数。您可以轻松地将其放入变量中或将其粘贴到另一个对象中。这基本上定义了this
在那个方法中是什么。还有一个"类"构造函数就是一个创建一个新对象的函数并告诉它,"如果有人在寻找你没有的属性,把它们转发给我的prototype
对象"然后在构造函数中执行该对象为this
的代码。
简而言之,这就是JS的原型继承,即使JS在此期间收到了class
关键字,这仍然是在幕后发生的事情。
我为什么要解释这个?
因为decorator正在处理那个原型对象。这一行api.get(path, descriptor.value.bind(target));
从原型中获取方法,将原型对象永久绑定为this
(因此结果函数只知道原型对象,永远不会看到任何实际实例),并使用绑定函数作为该路由的回调。
所以目前,即使那个类会神奇地被实例化(由谁;我不知道)你传递给路由的函数将不知道这个
国际海事组织。你的装饰器应该像这样:
function Get(path: string) {
return function (target: any, methodName: string) {
if(typeof target[methodName] !== "function"){
throw new Error("you need to use this decorator with a method.");
}
const Class = target.constructor;
api.get(path, (req: Request, res: Response) => {
const instance = diContainer.getInstance(Class); // or new Class();
return instance[methodName](req, res);
});
};
}
旁注:Dimava提出了这个话题;这些都是传统的装饰者。早在JS中有装饰器规范之前,TS就已经采用了它们。现在有了一个,它与这些遗留装饰器明显不同,TS最终在V5中实现了该规范。你(和我)应该了解新语法并采用它,因为这种语法可能很快就会被弃用。
你需要首先存储关于如何绑定路由的元数据,然后将其应用于类创建
https://tsplay.dev/mbvA4m (runnable)
import type { Request, Response } from 'lambda-api'
type LambdifiedProto = {
'_lambdifiedGetters'?: Record< /* path */ string, /* propertyKey */ string>
}
function Get(path: string) {
return function <K extends string, T extends (req: Request, res: Response) => Promise<any>>(
proto: Record<K, T>, propertyKey: K, descriptor: TypedPropertyDescriptor<T>
): void {
let lproto = proto as LambdifiedProto;
if (!Object.hasOwn(lproto, '_lambdifiedGetters')) {
// create or clone from protoproto
lproto._lambdifiedGetters = { ...lproto._lambdifiedGetters }
}
lproto._lambdifiedGetters![path] = propertyKey
console.log(`registered getter ${propertyKey} on path ${path}`)
}
}
function Lambda<C extends new (...a: any[]) => any>(klass: C) {
return class Lambdified extends klass {
constructor(...a: any[]) {
super(...a);
let getters = (klass.prototype as Lambdified)._lambdifiedGetters
for (let [path, propertyKey] of Object.entries(getters)) {
console.log('register api: ', { path, propertyKey, this: this })
// api.register(path, (q, s) => this[propertyKey](q, s))
}
}
}
}
@Lambda
class ProductApi {
me = 'ProductApi'
@Get('./health')
@Get('./v1/health')
async healthCheckr(req: Request, res: Response) {
console.log(`Executing -- GET /health`);
// this.someValue does not exists here
return res.status(200).json({ ok: true });
}
}
console.log('...create start...')
new ProductApi()