Search code examples
javascripthookclass-properties

How to execute a function before and after each class method call?


I want to insert on pre execute and post execute hooks on functions in javascript classes.

Lets say I have a class like this.

class Foo {
  method1(p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
  }

  method2(p3) {
    this.p3 = p3;
  }
}

I want to define a before and after hook for these preexisting class methods. Something like this.

class Foo {
  before(funName, ...params){
    // Should print ('method1', [p1, p2]) when method 1 is called
    // and ('method2', [p3]) when method 2 is called
    console.log(funName, params)
  }
  after(funName, result){
    // Should print the function name followed by its result
    console.log(funName, result)
  }
  method1(p1, p2) {
    this.p1 = p1;
    this.p2 = p2;
  }
  method2(p3) {
    this.p3 = p3;
  }
}

export default Foo;

What is the best way of implementing these hooks with minimal changes in existing code?


Solution

  • Here is a rough solution to the problem:

    // we iterate over all method names
    Object.getOwnPropertyNames(Foo.prototype).forEach((name) => {
    
      // First to do: we save the original method. Adding it to prototype
      // is a good idea, we keep 'method1' as '_method1' and so on
      Foo.prototype['_' + name] = Foo.prototype[name];
    
      // Next, we replace the original method with one that does the logging
      // before and after method execution. 
      Foo.prototype[name] = function() {
    
        // all arguments that the method receives are in the 'arguments' object
        console.log(`Method call: method1(${Object.values(arguments).join(', ')})`);
    
        // now we call the original method, _method1, on this with all arguments we received
        // this is probably the most confusing line of code here ;)
        // (I never user this['method'] before - but it works)
        const result = this['_' + name](...arguments);
    
        // here is the post-execution logging
        console.log(`Method result: ${result}`);
    
        // and we need to return the original result of the method
        return result;
      };
    });
    

    Please note that this code is not part of the class itself, execute it as a normal script.

    And there is a good chance that this short proof of concept crashes on real-world classes and requires some additional checks and special-case handlers, especially to get proper logging output. But it works with you Foo class.

    Here's the working example: https://codesandbox.io/s/great-fog-c803c