Method decorator that allows to execute a decorated method only once Typescript
I have a task to Implement a method decorator that allows to execute a decorated method only once.
For example:
class Test {
data: any;
@once
setData(newData: any) {
this.newData = newData;
}
}
const test = new Test();
test.setData([1,2,3]);
console.log(test.data); // [1,2,3]
test.setData('new string');
console.log(test.data); // [1,2,3]
I tried a lot of combinations to make a function that being called twice do nothing,but it's not what i should have and unit tests are failing,so,this is what i have till now:
const once = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => {
const method = descriptor.value;
descriptor.value = function (...args){
// ???
}
};
Unit tests:
describe('once', () => {
it('should call method once with single argument', () => {
class Test {
data: string;
@once
setData(newData: string) {
this.data = newData;
}
}
const test = new Test();
test.setData('first string');
test.setData('second string');
assert.strictEqual(test.data, 'first string')
});
it('should call method once with multiple arguments', () => {
class Test {
user: {name: string, age: number};
@once
setUser(name: string, age: number) {
this.user = {name, age};
}
}
const test = new Test();
test.setUser('John',22);
test.setUser('Bill',34);
assert.deepStrictEqual(test.user, {name: 'John', age: 22})
});
it('should return always return first execution result', () => {
class Test {
@once
sayHello(name: string) {
return `Hello ${name}!`;
}
}
const test = new Test();
test.sayHello('John');
test.sayHello('Mark');
assert.strictEqual(test.sayHello('new name'), 'Hello John!')
})
});
Could you help me please? Thanks in advance!
Solution 1:
reflect-metadata
is pretty handy for this scenario. You could try something like this:
import 'reflect-metadata';
const metadataKey = Symbol('initialized');
export function once(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const method = descriptor.value;
descriptor.value = function(...args) {
const initialized = Reflect.getMetadata(metadataKey, target, propertyKey);
if (initialized) {
return;
}
Reflect.defineMetadata(metadataKey, true, target, propertyKey);
method.apply(this, args);
}
}
You can find some more information on it here: https://www.typescriptlang.org/docs/handbook/decorators.html#metadata
Solution 2:
Try something like this:
const once = (
target: Object,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => {
const method = descriptor.value;
let isFirstTime = true;
descriptor.value = function (...args: any[]) {
if (!isFirstTime) { return; }
isFirstTime = false;
method(...args);
}
};