ES6 Proxy Function Call Trap
Le 9 juin 2015 à 07:58, Edwin Reynoso <eorroe at gmail.com> a écrit :
Or does someone know how to do what I'm trying to accomplish
Maybe the following code goes in the direction of what you want?
var people = new Proxy([new Person('Edwin', 'Reynoso'), new Person('That', 'Guy')], {
get(target, property) {
if (target[property] !== undefined)
return target[property];
var arr = [];
for (var person of target) arr.push(person[property]);
return new Proxy(arr, {
apply(target, thisArg, args) {
return target.map(item => Reflect.apply(item, thisArg, args))
}
})
}
});
About your proposal, I recall that, at some point in the history of ES6 draft, there was an [[Invoke]]
MOP, and a corresponding invoke
trap for proxies, that were finally eliminated.
Alright first I'll say I'm using FF to test because FF supports proxies, and I really don't know how to use Reflect and how it works yet. FF as well does not support Reflect right now. Therefore I'll look into that.
So from this source it seems that it would only work if the actual proxy is invoked as a function?
Take a look at the example in that link as well.
Now I'm not sure if Reflect changes that somehow.
BTW When I ran the code, I didn't get an Error
saying that Reflect
is not defined I got:
TypeError: people.logFullName is not a function
So I don't think that worked.
On Tue, Jun 9, 2015 at 3:44 AM, Edwin Reynoso <eorroe at gmail.com> wrote:
Alright first I'll say I'm using FF to test because FF supports proxies, and I really don't know how to use Reflect and how it works yet. FF as well does not support Reflect right now. Therefore I'll look into that.
That's right. Reflect is a key tool when you're writing proxy handlers. I'm working on implementing it in Firefox now. If you like, you can subscribe to this bug and get updates (quiet so far): bugzilla.mozilla.org/show_bug.cgi?id=987514
Here, I think the technique you're looking for might be that the proxy handler's get() method should return a new proxy (representing an array of methods). This new "method proxy" needs something callable as its target. When the method proxy's .call() trap is called, it'll receive the arguments.
Edwin,
The "call" trap you suggest has been suggested numerous times before (by the name of the "invoke" trap) and was nearly added to the spec at one point, but eventually removed because of a number of reasons, the more important one being that it breaks the equivalence that a method call in JS is nothing but a property access followed by a function call.
The suggestion made by Claude has nothing to do with Reflect, and you don't necessarily need Reflect to implement it: |Reflect.apply(fun, thisArg, args)| is equivalent to |Function.prototype.apply.call(fun, thisArg, args)|, it's just shorter and more idiomatic.
The important thing is to return, from your get
trap, another Proxy that
implements the apply
trap to be able to intercept the subsequent method
call.
Cheers, Tom
2015-06-09 17:25 GMT+02:00 Jason Orendorff <jason.orendorff at gmail.com>:
Ok so I got it work with the following code:
function Person(fName, lName, age) {
this.firstName = fName;
this.lastName = lName;
this.age = age;
}
Person.prototype = {
logFullName() {
console.log(this.firstName, this.lastName);
},
getAge() {
return this.age;
}
}
var people = new Proxy([new Person('Edwin', 'Reynoso', 16), new Person('That', 'Guy', 1000)], {
get(target, property) {
if(target[property] !== undefined) return target[property];
var arr = [];
for(var person of target) {
if(person[property].constructor === Function) {
return function(...args) {
for(var person of target) {
if(person[property]) arr.push(person[property](...args));
}
return arr;
}
} else if(person[property]) {
if(person[property]) arr.push(person[property]);
} else {
throw Error(property + ' is not defined');
}
}
return arr;
}
});
However I still believe there should be an invoke
trap in ES6 proxies.
First here's what it would look like:
var people = new Proxy([new Person('Edwin', 'Reynoso', 16), new Person('That', 'Guy', 1000)], {
get(target, property) {
if(target[property] !== undefined) return target[property];
var arr = [];
for(var person of target) {
if(person[property]) arr.push(person[property]);
}
return arr;
},
invoke(target, functionName, arguments) {
if(target[functionName]) target[functionName](...arguments);
var arr = [];
for(var person of target) {
if(person[functionName]) arr.push(person[functionName](...args));
}
return arr;
}
});
Now isn't that much easier to read and write?
Why have the handler.get
trap do all the work.
IMHO It's almost like saying only have one event listener for each type("click") for each element:
document.body.onclick = function() {
// handle first thing
// handle second thing
// handle third thing
}
The above could get really complicated
Over having the following:
document.body.addEventListener('click', function() {
// handle first thing
});
document.body.addEventListener('click', function() {
// handle second thing
}
document.body.addEventListener('click', function() {
// handle third thing
}
So why not do that for ES6 proxies as well by adding an invoke
trap.
If someone could give me a really good reason for why this is a bad idea, please do. I'm open minded.
So why not do that for ES6 proxies as well by adding an
invoke
trap.
Tom's response a couple of messages back summed it up fairly well.
To paraphrase, introducing "invoke" is a reasonable thing to propose (evidenced by the fact that it was part of the spec draft at one point). However, "invoke" in JS not a fundamental operation. It's always been a "get" followed by a "call". And since Proxies are all about allowing the user to define arbitrary behavior for the fundamental object model operations, it makes more sense to not have an "invoke" trap.
Could a userland library make writing these kinds of proxies more ergonomic?
Edwin,
I sympathize with your point-of-view, but we've debated all the pros and
cons of invoke
long ago, and unless new arguments are brought to the
table, I don't think it will be added again soon.
To clarify why the "invoke = get + call" equivalence is so important, consider for instance the common coding pattern of a "conditional" method call, where one only wants to invoke a method if it exists:
var foo = obj.foo;
if (foo) {
foo.call(obj)
}
With the current Proxy API, the get
trap returns a function (or a proxy
for a function), and the code works fine regardless of whether foo() is
called as a method, or as a function.
With the addition of invoke
, if you don't also provide a get
trap, then
the code will not behave as expected. Likely obj.foo
will return
undefined
, even though obj.foo()
would have triggered the invoke
trap
and done something useful.
So, any robust use of invoke
still requires implementing a get
trap
(to allow for all the use cases where methods are extracted as functions
from objects). As a result, it would lead to a net increase in
lines-of-code.
2015-06-11 15:48 GMT+02:00 Kevin Smith <zenparsing at gmail.com>:
Could a userland library make writing these kinds of proxies more ergonomic?
I don't see why not. The Proxy API offers only the bare minimum tools to create your own custom "exotic" objects. Arguably we need to grow some good libraries around it to make building these types of objects more routine (although I'd be the first to point out that you should avoid "exotic" objects unless there's a really compelling reason to go that route)
To be honest, you could simply subclass Proxy to add an invoke hook, if it's that simple:
function makeHandler(handler) {
let {get, invoke} = handler;
let lastGet = false;
let opts = {};
Object.keys(opts)
.filter(key => key !== 'get' && key !== 'call')
.map(key => [key, handler[key]])
.forEach(([key, func]) =>
opts[key] = function () {
lastGet = false;
func.apply(this, arguments);
});
opts.get = function (target, property) {
new Proxy(target[property], {
apply(f, thisArg, args) {
return invoke.call(handler, target, property, args);
},
});
return get.apply(this, arguments);
};
return opts;
}
class InvokeProxy extends Proxy {
constructor(target, handler) {
super(target, makeHandler(handler));
}
static revocable(target, handler) {
return Proxy.revocable(target, makeHandler(handler));
}
---------- Forwarded message ----------
From: Tom Van Cutsem <tomvc.be at gmail.com>
To: Kevin Smith <zenparsing at gmail.com>
Cc: es-discuss <es-discuss at mozilla.org>
Date: Thu, 11 Jun 2015 21:01:48 +0200 Subject: Re: ES6 Proxy Function Call Trap
Edwin,
I sympathize with your point-of-view, but we've debated all the pros and
cons of invoke
long ago, and unless new arguments are brought to the
table, I don't think it will be added again soon.
To clarify why the "invoke = get + call" equivalence is so important, consider for instance the common coding pattern of a "conditional" method call, where one only wants to invoke a method if it exists:
var foo = obj.foo;
if (foo) {
foo.call(obj)
}
With the current Proxy API, the get
trap returns a function (or a proxy
for a function), and the code works fine regardless of whether foo() is
called as a method, or as a function.
With the addition of invoke
, if you don't also provide a get
trap, then
the code will not behave as expected. Likely obj.foo
will return
undefined
, even though obj.foo()
would have triggered the invoke
trap
and done something useful.
So, any robust use of invoke
still requires implementing a get
trap
(to allow for all the use cases where methods are extracted as functions
from objects). As a result, it would lead to a net increase in
lines-of-code.
2015-06-11 15:48 GMT+02:00 Kevin Smith <zenparsing at gmail.com>:
Could a userland library make writing these kinds of proxies more ergonomic?
I don't see why not. The Proxy API offers only the bare minimum tools to create your own custom "exotic" objects. Arguably we need to grow some good libraries around it to make building these types of objects more routine (although I'd be the first to point out that you should avoid "exotic" objects unless there's a really compelling reason to go that route)
Let's say we have the following code:
class Person { constructor(fName, lName) { this.firstName = fName; this.lastName = lName; } logFullName() { console.log(this.firstName, this.lastName); } } var people = new Proxy([new Person('Edwin', 'Reynoso'), new Person('That', 'Guy')], { get(target, property) { if(target[property]) return target[property]; var arr = []; for(var person of target) arr.push(person[property]); return arr; } }); people.firstName // returns ["Edwin", "That"] people.lastName // returns ["Reynoso", "Guy"] people.logFullName // returns [Person.prototype.logFullName, Person.prototype.logFullName] people.forEach // returns forEach meaning any property of the acutal array is returned
I can't call logFullName unless it was a function of the target which is not (and not what I want bc I'll have to do that for every method)
Also I want the function to be called on each element if it could be called on it, if not return
undefined
So how about a call trap:
var people = new Proxy([new Person('Edwin', 'Reynoso'), new Person('That', 'Guy')], { call(target, functionName, arguments) { // target would be the array // functionName the name of the function // arguments the arguments passed in as an array } });
If the target has a method the
handler.get
of the proxy above would return that method and therefore you can call it. But what if no such method exist nosuchmethod would be the property yet how would we get the arguments?Another thing that could possibly done is whenever the property is nosuchmethod pass another argument which would be the actual arguments
Or does someone know how to do what I'm trying to accomplish