Gothiquo
Gothiquo

Reputation: 868

Run a function when any other class function is called

I have a es6 class with more then 100 functions.
I added a new function which I have to run each time any other function is called.
I found a lot of solutions but no one solved my problem...
I know I can add this call to each function but then I'll end with code repeat and a lot of work for all the functions.

That is that I've done so far:

return class Test {
    constructor () {}

    addedFN() {
         console.log('FN');
    }

    A() {
        this.addedFN();
        console.log('A');
    }

    B() {
        this.addedFN();
        console.log('B');
    }

    C() {
        this.addedFN();
        console.log('C');
    }
    ...
}

As we can see in this approach I have a lot of code duplication which I want to avoid...
Also, the solution to call the new function first and then inside to call the next function won't work, because all the functions are used in a lot of places in the application.

I tried also to run the following code in the constructor, but that is a bad solution as my class is called a lot of times and then this code will run each time:

for(const key of Object.getOwnPropertyNames(Test.prototype)) {
 const old = Test.prototype[key];
 Test.prototype[key] = function(...args) {
   addedFN(...args);
   old.call(this, ...args);
 };
}

Upvotes: 3

Views: 897

Answers (2)

VLAZ
VLAZ

Reputation: 29085

This might be a good use for a Proxy to transparently change your object without actually changing the object, just wrapping it in a proxy.

A get trap can check if you are accessing a method and then dynamically wrap it into a proxy with an apply trap.

class Test {
    A() { console.log('A'); }
    B() { console.log('B'); }
    C() { console.log('C'); }
    get X() { return 1; }
    Y = 2;
}

function addedFN() { console.log('FN'); }

const loggingHandler = {
  apply() {
    addedFN();
    return Reflect.apply(...arguments);
  }
};

const wrapMethods = {
  get() {
    const result = Reflect.get(...arguments);
    if (typeof result === "function")
      return new Proxy(result, loggingHandler);
    return result;
  }
};

const test = new Test();
const foo = new Proxy(test, wrapMethods);

foo.A();
foo.B();
foo.C();
console.log(foo.X);
console.log(foo.Y);
.as-console-wrapper { max-height: 100% !important; }

The approach can be generalised to accept different possible modifiers to methods and cache them in a WeakMap

class Test {
    A() { console.log('A'); }
    B() { console.log('B'); }
    C() { console.log('C'); }
    get X() { return 1; }
    Y = 2;
}

function addedFN() { console.log('FN'); }

const noop = () => {};
const wrapFunction = ({before = noop, after = noop}) => ({
  apply() {
    before();
    const result = Reflect.apply(...arguments);
    after();
    return result;
  }
});

const wrapMethods = handler => ({
  _cache: new WeakMap(),
  get() {
    const result = Reflect.get(...arguments);
    if (typeof result === "function") {
      if (!this._cache.has(result))
        this._cache.set(result, new Proxy(result, handler));
        
      return this._cache.get(result);
    }
    
    return result;
  }
});

const loggingHandler = wrapFunction({before: addedFN});

const test = new Test();
const foo = new Proxy(test, wrapMethods(loggingHandler));

foo.A();
foo.B();
foo.C();
console.log(foo.X);
console.log(foo.Y);

Upvotes: 3

Bravo
Bravo

Reputation: 6264

Don't run that in the constructor, run that after the class declaration

Also, that solution will result in too much recursion, since it will also apply to addedFn, which will call addedFn, which will call addedFn ... etc, until your browser throws a "too much recursion" error

So you also don't want to apply it to the addedFn

In the code below, I also don't apply it to constructor ... however, even when I do, it doesn't call addedFn when the object is constructed, so, to make it more clear, I explicitly don't touch the constructor

class Test {
  constructor(y) {
    this.y = y
  }
  fun1(x) {
    console.log('I am fun1', x, this.y);
  }
  fun2() {
    console.log('I am fun2');
  }
  addedFn(...x) {
    console.log('added function', ...x);
  }
}
Object.getOwnPropertyNames(Test.prototype).forEach(f => {
  if (f !== 'constructor' && f !== 'addedFn' && typeof Test.prototype[f] === 'function') {
    const old = Test.prototype[f];
    Test.prototype[f] = function(...args) {
      this.addedFn(...args);
      return old.apply(this, args);
    }
  }
})
let c = new Test('works');
c.fun1(1,2,3)

Note: you should also test if Test.prototype[f] is a function - unless your class ONLY has functions

Upvotes: 4

Related Questions