7

TL:DR; Is it possible to make a property of object to be invocable ( as a function ) only ?

What i mean by this

class Foo{
  bar(value){
    return value
  }
}

let newFoo = new Foo()

console.log(newFoo.bar(123))  // should work fine as function is invoked
console.log(newFoo.bar)  // here i need to throw or display an error instead of returning value

I tried to do this with Proxy and handler.get trap, but i have no clue how to capture whether it is a function call or just property access,

class Foo {
  bar(value) {
    return value
  }
}


const proxied = new Proxy(new Foo(), {
  get: function(target, prop, reciver) {
    if (prop === 'bar') {
      throw new Error('Bar is method need to be invoced')
    }
    return target[prop]
  }
})

console.log(proxied.bar(true))
console.log(proxied.bar)

I have also checked handler.apply but this also doesn't seems to be of no use as this is a trap on function, not on property

class Foo {
  bar(value) {
    return value
  }
}


const proxied = new Proxy(new Foo(), {
  apply: function(target, thisArg, argumentsList) {
    return target(argumentsList[0])
  },
  get: function(target, prop, reciver) {
    if (prop === 'bar') {
      throw new Error('Bar is method need to be invoced')
    }
    return target[prop]
  }
})

console.log(proxied.bar(true))
console.log(proxied.bar)

6
  • I don't think so, newFoo.bar will be evaluated as an expression before the subsequent argument list gets applied to the prior expression, so if accessing newFoo.bar throws, calling newFoo.bar would have to throw too Commented Sep 21, 2019 at 7:47
  • @CertainPerformance yeah that's what i thought cause . accessor will return property and then that will be invoked, but not sure, as there are always way to achieve a thing in JS, so hoping if there's anyway to do so Commented Sep 21, 2019 at 7:49
  • 1
    why do you need to do this, sounds like an XY problem Commented Sep 21, 2019 at 7:54
  • @JaromandaX i am trying to practice Proxies, and trying to mimic a strict environment ( or you can call it a linter kind of thing for the object ) just for learning purpose, this may be XY problem i am not sure though, i tired with whatever knowledge i have about proxies and couldn't succeed, so here i am asking from people if there's any other way it can be done, or in first place can it be done at all Commented Sep 21, 2019 at 7:59
  • 2
    @Code Maniac Since you have to get before call as far as I know, you can check if its called after the get, so something like this jsfiddle.net/y52khqs8 Commented Sep 21, 2019 at 8:42

1 Answer 1

2

No, this is not possible. There is no distinction between

const newFoo = new Foo()
newFoo.bar(123);

and

const newFoo = new Foo()
const bar = newFoo.bar;
Function.prototype.call.call(bar, newFoo, 123); // like `bar.call(newFoo, 123)`
// or Reflect.apply(bar, newFoo, [123]);

i.e. neither newFoo nor bar can distinguish these "from the inside". Now arbitrary things could happen in between the property access and the method call, and during the property access you cannot know what will happen next, so you cannot throw an exception prematurely. The method call might happen never (in newFoo.bar;), and there's no way to recognise that from newFoo alone.

The only approach would be to intercept all other accesses on newFoo and its properties, and throw after you detected a mischievous sequence; possibly having your "linter" check the sequence from the outside after the whole program ran:

const lint = {
  access: 0,
  call: 0,
  check() {
    console.log(this.access == this.call
      ? "It's ok"
      : this.access > this.call
        ? "method was not called"
        : "property was reused");
  },
  run(fn) {
    this.call = this.access = 0;
    try {
      fn();
    } finally {
      this.check();
    }
  }
}

function bar(value) {
  lint.call++; lint.check();
  return value;
}
class Foo {
  get bar() {
    lint.check(); lint.access++;
    return bar;
  }
}
lint.run(() => {
  const newFoo = new Foo;
  newFoo.bar(123);
});
lint.run(() => {
  const newFoo = new Foo;
  newFoo.bar;
});
lint.run(() => {
  const newFoo = new Foo;
  const bar = newFoo.bar;
  bar(123);
  bar(456);
});

The better solution would probably to write your own interpreter for simple expressions, which would only allow method calls.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks for your well detailed answer mate :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.