I have a typescript decorator factory which console logs total time taken to execute a function, actual function execution results and parameters passed to the decorator as
performaceLog
is supposed to work with prototype methods only because it relies on descriptor
, which should be optional.
There is no descriptor
in case of handleMessage = message => ...
class field because it doesn't exist on class prototype. Class fields are just assigned to this
in constructor.
This line won't work for same reason:
descriptor = Object.getOwnPropertyDescriptor(target, key);
In order to patch arrow method in decorator, a trap should be set on class prototype. Here is an example of universal decorator that can be used with both prototype and instance methods; it uses get
/set
to catch proper this
context and cache decorated function to patchFn
variable. It returns a descriptor regardless of descriptor
parameter:
function universalMethodDecorator(target: any, prop: string, descriptor?: TypedPropertyDescriptor<any>): any {
let fn;
let patchedFn;
if (descriptor) {
fn = descriptor.value;
}
return {
configurable: true,
enumerable: false,
get() {
if (!patchedFn) {
patchedFn = (...args) => fn.call(this, ...args);
}
return patchedFn;
},
set(newFn) {
patchedFn = undefined;
fn = newFn;
}
};
}
This applies only to TypeScript decorators. Babel legacy decorators may behave differently.
As explained in this answer, prototype methods can be preferred to instance methods for several reasons. One of the reasons is that they can be seamlessly decorated because decorators are applied to class prototype. The only real benefit of arrow method is that it is naturally bound to class instance, but since the decorator is already in use, prototype method can be bound in decorator if needed (this is what universalMethodDecorator
basically does; it is noop for arrow methods).