I currently have a Proxy object that I want to capture property calls to if the property is not defined.
A basic version of my code would be something like this.
var a = new Proxy({}, {
get: function(target, name, receiver) {
if (target in name) {
return target[name];
} else {
function a() {
return arguments;
}
var args = a();
return [target, name, receiver, args];
}
}
});
Property calls to a here (i.e: a.b; a.c() etc) should return the target, name, receiver and arguments of the property call.
The problem I wish to solve, however, requires me to know whether the property call is for a property or a function, such that I can apply different treatments to each. Checking the length of the arguments object does not work, as calling a.c() would yield a length of 0 just like a.b, so it would be treated as a plain property and not a method.
Is there a way, therefore, to identify whether the property attempting to be accessed is being called as a function or not.
UPDATE: I should clarify, this method needs to work if the accessed property/method is undefined, as well as existing.
It's possible in a very hacky way. We return a function if the property is undefined. If this function is called, then we know the user was trying to call the property as a function. If it never is, it was called as a property. To check if the function was called, we take advantage of the fact that a Promise's callback is called in the next iteration of the event loop. This means that we won't know if it's a property or not until later, as the user needs a chance to call the function first (as our code is a getter).
One drawback of this method is that the value returned from the object will be the new function, not undefined, if the user was expecting a property. Also this won't work for you if you need the result right away and can't wait until the next event loop iteration.
const obj = {
func: undefined,
realFunc: () => "Real Func Called",
prop: undefined,
realProp: true
};
const handlers = {
get: (target, name) => {
const prop = target[name];
if (prop != null) { return prop; }
let isProp = true;
Promise.resolve().then(() => {
if (isProp) {
console.log(`Undefined ${name} is Prop`)
} else {
console.log(`Undefined ${name} is Func`);
}
});
return new Proxy(()=>{}, {
get: handlers.get,
apply: () => {
isProp = false;
return new Proxy(()=>{}, handlers);
}
});
}
};
const proxied = new Proxy(obj, handlers);
let res = proxied.func();
res = proxied.func;
res = proxied.prop;
res = proxied.realFunc();
console.log(`realFunc: ${res}`);
res = proxied.realProp;
console.log(`realProp: ${res}`);
proxied.propC1.funcC2().propC3.funcC4().funcC5();
Would the typeof operator work for you?
For example:
if(typeof(a) === "function")
{
...
}
else
{
...
}
You can't know ahead of time whether it's a call expression or just a member expression, but you can deal with both situations simultaneously.
By returning a proxy targeting a deep clone of the original property that reflects all but two trap handlers to the original property, you can either chain or invoke each member expression.
The catch is that the proxy target also needs to be callable so that the handler.apply trap does not throw a TypeError:
function watch(value, name) {
// create handler for proxy
const handler = new Proxy({
apply (target, thisArg, argsList) {
// something was invoked, so return custom array
return [value, name, receiver, argsList];
},
get (target, property) {
// a property was accessed, so wrap it in a proxy if possible
const {
writable,
configurable
} = Object.getOwnPropertyDescriptor(target, property) || { configurable: true };
return writable || configurable
? watch(value === object ? value[property] : undefined, property)
: target[property];
}
}, {
get (handler, trap) {
if (trap in handler) {
return handler[trap];
}
// reflect intercepted traps as if operating on original value
return (target, ...args) => Reflect[trap].call(handler, value, ...args);
}
});
// coerce to object if value is primitive
const object = Object(value);
// create callable target without any own properties
const target = () => {};
delete target.length;
delete target.name;
// set target to deep clone of object
Object.setPrototypeOf(
Object.defineProperties(target, Object.getOwnPropertyDescriptors(object)),
Object.getPrototypeOf(object)
);
// create proxy of target
const receiver = new Proxy(target, handler);
return receiver;
}
var a = watch({ b: { c: 'string' }, d: 5 }, 'a');
console.log(a('foo', 'bar'));
console.log(a.b());
console.log(a.b.c());
console.log(a.d('hello', 'world'));
console.log(a.f());
console.log(a.f.test());
Open Developer Tools to view Console.
The Stack Snippets Console attempts to stringify the receiver in a weird way that throws a TypeError, but in the native console and Node.js it works fine.
Try it online!
Some ideas I've come up with, which achieve a similar result at a small cost:
A
typeof(a.b) === "function" //`false`, don't call it.
typeof(a.c) === "function" //`true`, call it.
//Maybe you're not intending to try to call non-functions anyways?
a.c();
B
get: function(target, property) {
//For this, it would have to already be set to a function.
if (typeof(target[property] === "function") {
}
}
C
a.b;
//Simply change the structuring a little bit for functions, e.g.:
a.func.c();
//Then, `func` would be set and handled as a special property.
Related
This question already has answers here:
Why is Proxy to a Map object in ES2015 not working
(2 answers)
Closed 4 years ago.
I have created a simple Proxy example on a Map Object. I can't figure out why the Set handler is not being executed and Get is executed instead, for Sets.
const handler = {
get: (targetObj, propName, receiverProxy) => {
console.log('PROXY: From get.')
let ret = Reflect.get(targetObj, propName, receiverProxy)
if (typeof ret === 'function') {
ret = ret.bind(targetObj)
}
return ret
},
set: (targetObj, propName, propValue, receiverProxy) => {
console.log('PROXY: From set.')
return Reflect.set(targetObj, propName, propValue, receiverProxy)
},
}
const targetMap = new Map([
['iron', 'man'],
])
const targetObj = {}
const proxy = new Proxy(targetMap, handler)
console.log(proxy.set('super', 'man'))
console.log(proxy.get('super'))
If you want a proxy setup that lets you intercept attempted calls to the Map .set() method, you'd have to do something like this:
let handler = {
get: (target, property, proxy) {
if (property === "set") {
console.log("intercepted a '.set' access on the proxied map");
return (key, value) => {
console.log("intercepted a 'set()' call on the proxied map");
return target.set(key, value);
};
}
return target[property];
}
};
The "get" handler method is called for any property access is attempted. In the method call
proxy.set("some key", "some value");
the "set" property of the object has to be looked up before the method can actually be called. It's that lookup operation that results in the handler "get" method invocation.
Now if you make the proxy as in your code and then do
proxy.set("some key", "some value");
you'll see that the log message fires when that "interceptor" function you returned is invoked.
Set and get doesn't work like that.
You can use the proxy object like any normal object. For example:
proxy.super = 'man'; // fires the setter
const x = proxy.super; // fires the getter
Then, in your get/set handlers you can call the original get/set methods on the targetObj.
I have the following class that is utilizing a Proxy for getting properties and methods:
class User extends Model {
static table = 'users';
_attributes = {
id: 1,
firstName: 'Testing',
lastName: 'Test'
};
constructor() {
return new Proxy(this, {
get: function(target, name) {
// proxy getting code for functions and properties
}
});
}
client() {
return this.hasOne(Client, 'clientID');
}
}
Within the get method of the proxy, retrieving attributes is trivial. I just check for their existence within _attributes, and return the value, otherwise null.
if (target._attributes.hasOwnProperty(propertyName)) {
return target._attributes[name];
}
return null;
Then I can use it as:
const user = new User();
console.log(user.id); // returns 1
console.log(user.firstName); // returns "Testing"
Within the get method of proxy, I can also check if the property called was a function, and return the appropriate function as a result:
if (typeof target[name] === 'function') {
const originalFunction = target[name];
return function(...args) {
return originalFunction.apply(this, args);
};
}
Then I can use it as:
const user = new User();
user.client(); // Returns the return value of client() from the class
However, within the get function of the proxy, I am unable to differentiate between user.client() and user.client. In the first case, I want to return the result of the function. In the second case, I want to retrieve the result of the function, perform an additional step, and then return that.
In Pseudo-code:
if (property is a function call) {
return property();
}
else if (property is a function, but not a function call) {
return property().doSomethingElse();
}
Using a Proxy, can I tell the difference between user.client() and user.client from within the get method?
In PHP, this is possible using the magic methods __get vs __call, but I am looking for a way to do this in Javascript.
I want to have getter for initialize method of object
var phas = new Proxy({b:9,
cont:0,
statistic:function(){
console.log(this.cont)
this.cont++
}
}, {
has: function (target, key) {
if (key == "a") return false;
return true;
},
apply: function () {
console.log('run call ')
}
}
)
phas.run();
Uncaught TypeError: phas.run is not a function
manual for proxy object https://www.xul.fr/javascript/proxy.php
You seem to have misunderstood how proxies work.
When you create a proxy, you create a proxy on that object. The proxy will not automatically extend itself to the object's properties.
The apply trap is only applicable on functions, if you proxied a function, and then called it, it would work as you expect.
If you want to create methods dynamically, you will need to do something like this instead:
var p = new Proxy({}, {
get: function(target, prop) {
// If the property exists, just return it
if (prop in target)
return target[prop];
// Otherwise, return a function
return function() { console.log("Some method", prop); };
}
});
p.run() //Logs: Some method run
typeof p.a == 'function' // true
I am using Proxy to Proxy an object. The getter and setter work fine like expected. However, the apply method is never called.
var p = new Proxy({}, {
/* getter */
get(target, name) {
return target[name]
},
/* setter */
set(target, name, value) {
target[name] = value
},
/* supposedly called apply */
apply(target,that,arg) {
console.log('apply is called, what to do here?')
}
})
This way, I can assign something to p or return something even if it doesn't exist.
When I for instance let the getter function return this
get(target, name) {
return 'getting ' + name
},
and then console.log(p.flappy) I will get the response "getting flappy" even when it doesn't exist.
So far so good but when I try to call flappy doing p.flapppy() it wil throw an error that flappy is not a function.
This is still somewhat obvious because the getter does not return a function. When I let the getter return a function like this
get(target, name) {
return function() { return 'this is '+name }
},
I can call the property without it having to exist.
console.log(
p.flappy() // this is flappy!
)
So when does apply get called? Not in the snippet I just showed here and also not in this case:
p.foo = function() {
console.log('yay!')
return 'foo!'
}
It does not work to do p.foo() or p.foo.call() or p.foo.apply(), in neither cases apply is called.
The ultimate purpose of this journey is that I want to return a DIFFERENT value depending on whether a property is being read or being called. Like this:
p.someNewProperty // return what the getter function returns
p.anotherProperty() // return something else here because this is a function call
Is this possible?
I know this is question is a year old, but I ran into this as well and I found a way to do what you are trying to do. So this is for future reference, as I didn't find correct solutions elsewhere.
Short version: accessing functions inside an object (or a class) is essentially getting the property of the object that has the function. The trick is to return another Proxy with apply so you can proxy these functions correctly.
Consider the following object:
const myObject = {
a: 'Hello world!',
b: x => x * x
};
Accessing a or b shall both be caught by a Proxy's get, because they are properties of the object. You should catch all get and then filter for functions. Once you have a function, you return a new Proxy that catches this particular function with Proxy.apply.
Then, to let the function execute as intended, inside the Proxy.apply we return a Reflect.apply, which calls the original function with the correct arguments as expected.
You will end up with this code:
const myProxyObject = new Proxy(myObject, {
get(target, propKey, receiver) {
// Calling functions
if (typeof target[propKey] === 'function') {
return new Proxy(target[propKey], {
apply(applyTarget, thisArg, args) {
console.log(`Calling ${thisArg.constructor.name}.${propKey}(${args})`);
return Reflect.apply(applyTarget, thisArg, args);
}
});
}
// Accessing properties
if (target.hasOwnProperty(propKey)) {
console.log(`Get value of ${target.constructor.name}.${propKey}`);
console.log(`Value: ${target[propKey]}`);
}
return target[propKey];
}
});
Demo on jsfiddle
You don't get the result of the function, because that would require you to execute it.
Note: it is possible to use this with classes and it works very nicely. The only caveat is that your Proxy will be catching all internal functions as well. In order to prevent logging dozens of valueOfs, I highly recommend to test if a function is native or not with something like this isNative function
As documented on MDN, the apply proxy method is for proxying a function call on the proxy object itself, not a call on a method of the object.
It only works with functions (as the Proxy target), not regular object instances, but here is how it would work:
var p = new Proxy(function() {}, {
apply: function() {
console.log('apply called');
}
});
p();
The ultimate purpose of this journey is that I want to return a DIFFERENTÂ value depending on whether a property is being read or being called.
It is not possible to directly do what you intend, nor would it really make sense. To call is to read the property.
and after some years...
yes, you can! you can return a DIFFERENT value depending on whether a property is being read or being called!
const authUser = { id: 1 }
const user = new Proxy(function () {}, {
get (target, property) {
return authUser.id
},
apply (target, thisArg, args) {
// some stuff to get user
return { id: args[0] }
}
})
console.log(user.id)
console.log(user(2).id)
or you can use two step proxy.
const authUser = { id: 1 }
const user = new Proxy(function () {}, {
get (target, property) {
return userProxy(authUser.id, property)
},
apply (target, thisArg, args) {
return userProxy(args[0])
}
})
function userProxy(id, property) {
// some stuff to get user
const user = { id }
return property ? user[property] : new Proxy(user, {
get (target, property) {
return user[property]
}
})
}
console.log(user.id)
console.log(user(2).id)
I'm trying to use Javascript Proxy objects to trap the arguments that are passed to a 'method' of the target that I'm proxying.
Please consider this example:
var test = {
doSomething: function() {
console.log( arguments.length );
}
};
var testProxy = new Proxy( test, {
get: function( target, property, receiver ) {
// I'd like to have access to any arguments when
// the property being accessed here is a function
// that is being called
return target[ property ];
}
} );
testProxy.doSomething( 'this', 'is', 'lame' ); // I want to trap those arguments
It appears that these Proxy objects only allow you to trap accessing the property, but not the actual function call, with its arguments, when the property is in fact a function.
After reflecting a bit on the matter, I "get" (pardon the pun) that the get method is just intended for property access, in stead of invocation, but then I would have expected to be able to define something like a call method in the Proxy as well.
Perhaps it's doable with defining an apply method in the Proxy, but then I'd probably have to create a Proxy object for each individual method of the object I want to proxy; and that's not what I am after.
Unless I'm overlooking an actual alternative possibility here: how is it that this is overlooked in the Proxy implementation?! Isn't the whole point of a proxy to be able to intercept method calls and their arguments as well?
Or is this yet another misunderstanding of Javascript, on my part, about Javascript not being a 'classical' OOP language, and that the functionality I'm looking for wouldn't actually make sense in the context of Javascript?
There actually is a way to do this, of course! I just hadn't thought it through thoroughly enough. I can just return a 'proxy' function and trap the arguments in there:
var test = {
doSomething: function() {
console.log( arguments.length );
}
};
var testProxy = new Proxy( test, {
get: function( target, property, receiver ) {
switch( property ) {
case 'doSomething':
// you just have to return a proxy function
return function() {
// arguments accessible, after all!
console.log( 'testProxy::doSomething() arguments.length: ' + arguments.length );
// here you can still invoke the original method, of course
target[ property ].apply( this, arguments );
}
break
}
return target[ property ];
}
} );
testProxy.doSomething( 'this', 'is', 'not', 'so', 'lame', 'after', 'all' );
another snippet : )
const obj_hidden = {};
const obj = new Proxy(obj_hidden, {
get(target, prop) {
if (typeof target[prop] == 'function') {
return function (...args) {
console.dir({ call: [prop, ...args] });
return target[prop].apply(target, args);
}
}
console.dir({ get: prop });
return target[prop];
},
set(target, prop, value) {
console.dir({ set: [prop, value] });
target[prop] = value;
return true;
}
});
Here's another snippet
var test = {
doSomething: function() {
console.log( arguments.length );
}
};
var testProxy = new Proxy( test, {
get: function( target, property, receiver ) {
// to have access to any arguments
return ( ...args ) => target[property].apply(target, args);
}
});
const value = testProxy.doSomething( 'this', 'is', 'lame' ); // those arguments will be trapped
Thanks for sharing your answer. It helped me figure out how to fix my problem, which is fairly similar to this one. I figured I share mine as well, maybe it will be helpful.
I intended to wrap the arguments of the callback function passed to promise objects(the resolve and reject function when creating a new promise). So I created a proxy for Promise object to modify the constructor, but in the constructor, I couldn't access the arguments of the first argument of the promise constructor. This is how I did it, thanks to Decent's answer
// Wrap promise:
let promiseWrapperHandlers = {
construct: function(target, args) {
let originalCb = args[0]
if (typeof args[0] === 'function') {
let wrappedCb = function() {
let resFn = arguments[0] || (() => {})
let wrappedResolve = function(v) {
console.log("resolving promise with " + v);
return resFn(v);
}
let rejFn = arguments[1] || (() => {})
let wrappedReject = function(err) {
console.log("rejecting promise with " + err);
return rejFn(err);
}
return originalCb(wrappedResolve, wrappedReject)
}
args[0] = wrappedCb
}
let p = new target(...args)
return p
},
}
const RealPromise = Promise
Promise = new Proxy(RealPromise, promiseWrapperHandlers)
// END wrap promise
const p = new Promise((resolve, reject) => {
resolve(122)
})
const p2 = new Promise((resolve, reject) => {
reject(121)
})