最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Typescript decorators not working with arrow functions - Stack Overflow

programmeradmin0浏览0评论

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 well.

e.g.

export function performaceLog(...args: any[]) {
return (target: Object, key: string, descriptor: TypedPropertyDescriptor<any>) => {
    var msg = '';
    if (args.length != 0) {
        msg = args.map(arg => arg).join(' ');
    }

    if (descriptor === undefined) {
        descriptor = Object.getOwnPropertyDescriptor(target, key);
    }

    if (typeof descriptor.value !== 'function') {
        throw new SyntaxError('Only functions can be used with log decorators');
    }

    var originalMethod = descriptor.value.bind(target);

    descriptor.value = function() {
        var funcArgs: any = [];
        for (var i = 0; i < arguments.length; i++) {
            funcArgs[i - 0] = arguments[i];
        }
        var startTime = performance.now();
        console.log(`Begin function ${key} with params (${funcArgs}) : ${msg}`);
        var result = originalMethod.apply(this, funcArgs);
        var endTime = performance.now();
        console.log(`End function ${key}. Execution time: ${endTime - startTime} milliseconds. Return result : ${result}`);
        return result;
    };
    return descriptor;
};
}

I am using the above decorator with a function which is present in a class: (considering my class has other methods as well like ctor, get, set and utils).

class LoggerService {
    @performaceLog('validate', 'message')
    handleMessage(message) {
        console.log(message);
        return true;
    };
}

Function call looks like this:

handleMessage('decoratorValidation');

This gives me perfect output as:

Begin function handleMessage with params (decoratorValidation) : validate message     
decoratorValidation
End function handleMessage. Execution time: 0.3000000142492354 milliseconds. 
Return result : true

But when I change the function handleMessage to support arrow format (ES6), it throws me an error:

@performaceLog('validate', 'message')
handleMessage = message => {
    console.log(message);
    return true;
};

Error Message:

Unable to resolve signature of property decorator when called as an expression.

I have all the parameters set correctly in my tsconfig.json. I have my entire project supporting "ES6" target and I want the decorator to support arrow functions.

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 well.

e.g.

export function performaceLog(...args: any[]) {
return (target: Object, key: string, descriptor: TypedPropertyDescriptor<any>) => {
    var msg = '';
    if (args.length != 0) {
        msg = args.map(arg => arg).join(' ');
    }

    if (descriptor === undefined) {
        descriptor = Object.getOwnPropertyDescriptor(target, key);
    }

    if (typeof descriptor.value !== 'function') {
        throw new SyntaxError('Only functions can be used with log decorators');
    }

    var originalMethod = descriptor.value.bind(target);

    descriptor.value = function() {
        var funcArgs: any = [];
        for (var i = 0; i < arguments.length; i++) {
            funcArgs[i - 0] = arguments[i];
        }
        var startTime = performance.now();
        console.log(`Begin function ${key} with params (${funcArgs}) : ${msg}`);
        var result = originalMethod.apply(this, funcArgs);
        var endTime = performance.now();
        console.log(`End function ${key}. Execution time: ${endTime - startTime} milliseconds. Return result : ${result}`);
        return result;
    };
    return descriptor;
};
}

I am using the above decorator with a function which is present in a class: (considering my class has other methods as well like ctor, get, set and utils).

class LoggerService {
    @performaceLog('validate', 'message')
    handleMessage(message) {
        console.log(message);
        return true;
    };
}

Function call looks like this:

handleMessage('decoratorValidation');

This gives me perfect output as:

Begin function handleMessage with params (decoratorValidation) : validate message     
decoratorValidation
End function handleMessage. Execution time: 0.3000000142492354 milliseconds. 
Return result : true

But when I change the function handleMessage to support arrow format (ES6), it throws me an error:

@performaceLog('validate', 'message')
handleMessage = message => {
    console.log(message);
    return true;
};

Error Message:

Unable to resolve signature of property decorator when called as an expression.

I have all the parameters set correctly in my tsconfig.json. I have my entire project supporting "ES6" target and I want the decorator to support arrow functions.

Share Improve this question edited Apr 19, 2018 at 20:07 Shreyas Patil asked Apr 19, 2018 at 18:53 Shreyas PatilShreyas Patil 631 silver badge8 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 9

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

发布评论

评论列表(0)

  1. 暂无评论