2

I'm trying to build an AOP logger for my classes... I'm having an issue where when i reflect back to the targeted function, the function loses access to this

so my AOP kinda looks like this

AOP.js

class AOP {
  constructor() {

  }
  static ClassHandler(obj) {
    const InstanceHandler = {
      get(target, prop, receiver) {
        console.log(target.constructor.name);
        const origMethod = target[prop];
        return function (...args) {
          // let result = Reflect.apply(origMethod, this, args)
          let result = Reflect.get(target, prop, receiver)
          result = Reflect.apply(result, this, args);
          console.log(prop + JSON.stringify(args)
              + ' -> ' + JSON.stringify(result));
          return result;
        };
      },
      apply(target, thisArg, argumentsList) {
        console.log('actually applied');
      }
    }

    const handler = {
      construct(target, args) {
        console.log(`${target.name} instantiated`);
        console.log(args);
        const instance = Reflect.construct(...arguments);
        return new Proxy(instance, InstanceHandler);
      }
    }

    return new Proxy(obj, handler);
  }
}

module.exports = AOP;

A singleton

OtherClass.js

class OtherClass {
  constructor() {
    this._blah = 'this is a shoutout';
  }

  shoutOut() {
    console.log(this._blah);
  }
}

module.exports = new OtherClass();

and a class which requires the singleton

CalculatorDI.js

class Calculator {
  constructor(otherClass) {
    this.otherClass = otherClass;
  }

  add(a, b) {
    this.otherClass.shoutOut();
    return a+b;
  }

  minus(a, b) {
    return a-b;
  }
}

module.exports = Calculator;

bringing it all together like this:

const AOP = require('./src/aspects/AOP');
const Calculator = AOP.ClassHandler(require('./src/CalculatorDI'));
const otherClass = require('./src/OtherClass');
const calculator = new Calculator(otherClass);

calculator.add(1,1);

When running this, I get the error:

TypeError: this.otherClass.shoutOut is not a function

Jarede
  • 3,310
  • 4
  • 44
  • 68

1 Answers1

1

Your problem is that your proxy always returns a function, for any property that is accessed, including this.otherClass. You will need to use

const instanceHandler = {
  get(target, prop, receiver) {
    console.log(target.constructor.name);
    const orig = Reflect.get(target, prop, receiver);
    if (typeof orig == "function") {
//  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      return function (...args) {
        const result = orig.apply(this, args);
        console.log(prop + JSON.stringify(args) + ' -> ' + JSON.stringify(result));
        return result;
      };
    } else {
      return orig;
    }
  }
};

Also notice that you don't need an apply trap in the instanceHandler, as none of your instances is a function.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • is there a reason you're doing `orig.apply` rather than `Reflect.apply`? – Jarede Jul 09 '18 at 13:31
  • 1
    @Jarede Mostly because the `apply` has nothing to do with the proxy but is just the usual function decoration. – Bergi Jul 09 '18 at 13:50
  • So using `Reflect.apply` shouldn't cause issues? I knocked this up https://runkit.com/5a7af2d1bd09580012f31fc2/5b43584ff93f080011766477 and it works with `Reflect.apply` but not with `orig.apply` – Jarede Jul 09 '18 at 13:52
  • @Jarede Calling it on the `target` instead of the `this` is wrong, that implicitly binds the method. – Bergi Jul 09 '18 at 14:10
  • So how would i get around the issue within that code sample? your code "looks" like it breaks it, whereas my code returns what i expect. – Jarede Jul 09 '18 at 14:24
  • @Jarede What is the issue? I can't run your code with the ajv so I don't see what error it throws. – Bergi Jul 09 '18 at 15:58
  • it's not so much an error, it's more that with your code returns something completely different to the Reflect.apply code. I'm expecting an object with an errors attribute, when run with your code it seems to return a function or a blank object. – Jarede Jul 09 '18 at 16:06
  • also i've fixed the runkit code so you just need to comment between the 2 statements. – Jarede Jul 09 '18 at 16:06