Home > Software engineering >  Debugging a try/catch class decorator to be used around a standard typescript class. Reflect.ownKeys
Debugging a try/catch class decorator to be used around a standard typescript class. Reflect.ownKeys

Time:12-29

I am debugging this dude's code trying to make it work around my TypeScript Data Access Layer object as a "console.log; then throw the error anyway" type decorator.

I expect the code in the 2nd arg to the decorator to be executed when an error throws in my class's methods.

My actual result is closer to nothing. I'm not sure it's exactly nothing: Somehow my controller returns "name": "SequelizeEagerLoadingError" in json. However, this is not what I want. I think it'll be obvious when you see the code.

@TryCatchClassDecorator(EagerLoadingError, (err, context) => {
    console.log(context, err); // you'd think this would cause it to print!
    throw err;
})
class TaskDAO {
    constructor() {}

   public getScorecard = async (providerName: ProviderEnum, batchNum: number) => {
        return await Task.findAll({ where: { providerName }, include: { model: Batch, where: { batchId: batchNum } } });
    };
}

Lots of methods are cut out but the one is good enough.

Were I to remove the failing decorator and try/catch that getScorecard when it throws an error, I would see:

EagerLoadingError [SequelizeEagerLoadingError]: Batch is associated to Task using an alias. You must use the 'as' keyword to specify the alias within your include statement.
    at Function._getIncludedAssociation (/home/rlm/Code/apGymBE/node_modules/sequelize/src/model.js:754:13)
    at Function._validateIncludedElement (/home/rlm/Code/apGymBE/node_modules/sequelize/src/model.js:646:53)
    at /home/rlm/Code/apGymBE/node_modules/sequelize/src/model.js:542:37
    at Array.map (<anonymous>)
    at Function._validateIncludedElements (/home/rlm/Code/apGymBE/node_modules/sequelize/src/model.js:537:39)
    at Function.findAll (/home/rlm/Code/apGymBE/node_modules/sequelize/src/model.js:1785:12)

But with the decorator active, I do not get anything like that!

So it's like, let's go look at the decorator's innards. Maybe we can deduce what is going on.

It sure looks to me like this block is supposed to do something huh?

Yet when I stick a bunch of console.logs above it, its apparent the wrong thing is happening.

console.log(target.prototype, "18rm"); // fixme: target.prototype is empty! // fails!
console.log(Object.getOwnPropertyNames(target.prototype), "19rm"); // outputs [ 'constructor' ] 
console.log(Reflect.ownKeys(target.prototype), "20rm"); // outputs [ 'constructor' ] 

for (const propertyName of Reflect.ownKeys(target.prototype).filter(prop => prop !== "constructor")) {
                console.log(propertyName, "21rm");
                const desc = Object.getOwnPropertyDescriptor(target.prototype, propertyName)!;
                console.log(desc, errorType, "19rm");
                const isMethod = desc.value instanceof Function;
                if (!isMethod) continue;
                Object.defineProperty(target.prototype, propertyName, _generateDescriptor(desc, errorType, handler));
            }

I think the target.prototype maybe should have all the class's methods on it!

target.prototype is an empty object on class decorator is this thread's title. The answer states "The methods/properties created through the class syntax are non-enumerable. Thus, you would need to use Object.getOwnPropertyNames() which returns an array of all properties (including non-enumerable properties)"

But I tried that. It outputs [ 'constructor' ] and nothing else. How can this be what I want?

I can confirm that simply console.logging console.log(target) prints the class I am decorating. So at least that is going right.

edit: By the comment // Iterate over class properties except constructor I presume the correct move is to get the class's methods here.

edit2: The creator of this code says "We use Reflect.ownKeys(target.prototype) to obtain the name of all its (non static) methods" hence the bug is indeed that the method Reflect.ownKeys() is just ["constructor"]

CodePudding user response:

I think the target.prototype maybe should have all the class's methods on it!

Not when you've made the mistake to create getScorecard as a class field instead of defining a proper method. Don't do that! The .getScorecard only gets created as an instance property inside the constructor. There is no (easy) way to reach it from a decorator.

Fix your class declaration and the decorator should work:

@TryCatchClassDecorator(EagerLoadingError, (err, context) => {
    console.log(context, err);
    throw err;
})
class TaskDAO {
    constructor() {}

    public async getScorecard(providerName: ProviderEnum, batchNum: number) {
//         ^^^^^             ^                                            ^^^
        return await Task.findAll({ where: { providerName }, include: { model: Batch, where: { batchId: batchNum } } });
    }
//  ^
}
  • Related