Why is Set not executed when using Proxy on Map Object? [duplicate] - javascript

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.

Related

Observe changes to a Map using a Proxy [duplicate]

This question already has answers here:
Why is Proxy to a Map object in ES2015 not working
(2 answers)
Closed 5 years ago.
I use a Map javascript object. I'd like to observe changes to a Map instance using a Proxy object. However, whatever handler object I try to feed in to the proxy, I consistently get the error Method Map.prototype.set called on incompatible receiver. What am I doing wrong? The idiomatic example I expect to work looks like
var map = new Map();
var proxy = new Proxy(map, {
set(obj, prop, value) {
console.log(value);
}
});
proxy.set(1, 1); --> error
I also tried setting a handler for apply, but to no avail.
First off, understand that your error also reproduces with just
var map = new Map();
var proxy = new Proxy(map, {});
proxy.set(1, 1);
It is not related to your usage of set(obj, prop, value).
Why it fails
To break that a bit more, understand that this is basically the same as doing
var map = new Map();
var proxy = new Proxy(map, {});
Map.prototype.set.call(proxy, 1, 1);
which also errors. You are calling the set function for Map instances, but passing it a Proxy instead of a Map instance.
And that is the core of the issue here. Maps store their data using an private internal slot that is specifically associated with the map object itself. Proxies do not behave 100% transparently. They allow you to intercept a certain set of operations on an object, and perform logic when they happen, which usually means proxying that logic through to some other object, in your case from proxy to map.
Unfortunately for your case, proxying access to the Map instance's private internal slot is not one of the behaviors that can be intercepted. You could kind of imagine it like
var PRIVATE = new WeakMap();
var obj = {};
PRIVATE.set(obj, "private stuff");
var proxy = new Proxy(obj, {});
PRIVATE.get(proxy) === undefined // true
PRIVATE.get(obj) === "private stuff" // true
so because the object pass as this to Map.prototype.set is not a real Map, it can't find the data it needs and will throw an exception.
Solution
The solution here means you actually need to make the correct this get passed to Map.prototype.set.
In the case of a proxy, the easiest approach would be to actually intercept the access to .set, e.g
var map = new Map();
var proxy = new Proxy(map, {
get(target, prop, receiver) {
// Perform the normal non-proxied behavior.
var value = Reflect.get(target, prop, receiver);
// If something is accessing the property `proxy.set`, override it
// to automatically do `proxy.set.bind(map)` so that when the
// function is called `this` will be `map` instead of `proxy`.
if (prop === "set" && typeof value === "function") value = value.bind(target);
return value;
}
});
proxy.set(1, 1);
Of course that doesn't address your question about intercepting the actual calls to .set, so for that you can expand on this to do
var map = new Map();
var proxy = new Proxy(map, {
get(target, prop, receiver) {
var value = Reflect.get(target, prop, receiver);
if (prop === "set" && typeof value === "function") {
// When `proxy.set` is accessed, return your own
// fake implementation that logs the arguments, then
// calls the original .set() behavior.
const origSet = value;
value = function(key, value) {
console.log(key, value);
return origSet.apply(map, arguments);
};
}
return value;
}
});
proxy.set(1, 1);

Determining if get handler in Proxy object is handling a function call

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.

Why is Proxy to a Map object in ES2015 not working

I'm running the following script through Google Chrome Version 57.0.2987.133:
var loggingProxyHandler = {
"get" : function(targetObj, propName, receiverProxy) {
let ret = Reflect.get(targetObj, propName, receiverProxy);
console.log("get("+propName.toString()+"="+ret+")");
return ret;
},
"set" : function(targetObj, propName, propValue, receiverProxy) {
console.log("set("+propName.toString()+"="+propValue+")");
return Reflect.set(targetObj, propName, propValue, receiverProxy);
}
};
function onRunTest()
{
let m1 = new Map();
let p1 = new Proxy(m1, loggingProxyHandler);
p1.set("a", "aval"); // Exception thrown from here
}
onRunTest();
NOTE: Requires a browser supporting ES2015's Proxy
When run, I see the handler's get trap is called to return the Map's set function
and then I receive the following error:
"Uncaught TypeError: Method Map.prototype.set called on incompatible receiver [object Object]"
at Proxy.set (native)
...
I tried removing the trap functions from the loggingProxyHandler (making it an empty object) but still receive the same error.
My understanding was that a Proxy object was supposed to be able to generated for all native ES5 and ES2015 javascript objects. Array seems to work well under the same proxy handler.
Did I misunderstand the specs?
Is my code missing something?
Is there a known bug in Chrome? (I did a search and found no defects for Chrome on this subject.)
The reason you're getting the error is that the proxy isn't getting involved in the p1.set() method call (other than that the get trap is used to retrieve the function reference). So once the function reference has been retrieved, it's called with this set to the proxy p1, not the map m1 — which the set method of a Map doesn't like.
If you're really trying to intercept all property access calls on the Map, you can fix it by binding any function references you're returning from get (see the *** lines):
const loggingProxyHandler = {
get(target, name/*, receiver*/) {
let ret = Reflect.get(target, name);
console.log(`get(${name}=${ret})`);
if (typeof ret === "function") { // ***
ret = ret.bind(target); // ***
} // ***
return ret;
},
set(target, name, value/*, receiver*/) {
console.log(`set(${name}=${value})`);
return Reflect.set(target, name, value);
}
};
function onRunTest() {
const m1 = new Map();
const p1 = new Proxy(m1, loggingProxyHandler);
p1.set("a", "aval");
console.log(p1.get("a")); // "aval"
console.log(p1.size); // 1
}
onRunTest();
NOTE: Requires a browser supporting ES2015's Proxy
Notice that when calling Reflect.get and Reflect.set, we don't pass along the receiver (in fact, we're not using the receiver argument at all in those, so I've commented the parameter out). That means they'll use the target itself as the receiver, which you need if the properties are accessors (like Map's size property) and they need their this to be the actual instance (as Map's size does).
If your goal is just to intercept Map#get and Map#set, though, you don't need a proxy at all. Either:
Create a Map subclass and instantiate that. Assumes you control the creation of the Map instance, though.
Create a new object that inherits from the Map instance, and override get and set; you don't have to be in control of the original Map's creation.
Replace the set and get methods on the Map instance with your own versions.
Here's #1:
class MyMap extends Map {
set(...args) {
console.log("set called");
return super.set(...args);
}
get(...args) {
console.log("get called");
return super.get(...args);
}
}
const m1 = new MyMap();
m1.set("a", "aval");
console.log(m1.get("a"));
#2:
const m1 = new Map();
const p1 = Object.create(m1, {
set: {
value: function(...args) {
console.log("set called");
return m1.set(...args);
}
},
get: {
value: function(...args) {
console.log("get called");
return m1.get(...args);
}
}
});
p1.set("a", "aval");
console.log(p1.get("a"));
#3:
const m1 = new Map();
const m1set = m1.set; // Yes, we know these are `Map.prototype.set` and
const m1get = m1.get; // `get`, but in the generic case, we don't necessarily
m1.set = function(...args) {
console.log("set called");
return m1set.apply(m1, args);
};
m1.get = function(...args) {
console.log("get called");
return m1get.apply(m1, args);
}
m1.set("a", "aval");
console.log(m1.get("a"));
Let me add more to this.
Many built-in objects, for example Map, Set, Date, Promise and others make use of so-called internal slots.
These are like properties but reserved for internal, specification-only purposes. For instance, Map stores items in the internal slot [[MapData]]. Built-in methods access them directly, not via [[Get]]/[[Set]] internal methods. So Proxy can’t intercept that.
For example:
let map = new Map();
let proxy = new Proxy(map, {});
proxy.set('name', 'Pravin'); // Error
Internally, a Map stores all data in its [[MapData]] internal slot. The proxy doesn't have such slot. The built-in method Map.prototype.set method tries to access the internal property this.[[MapData]], but because this=proxy, can't find it in proxy and just fails.
There’s a way to fix it:
let map = new Map();
let proxy = new Proxy(map,{
get(target,prop,receiver){
let value = Reflect.get(...arguments);
return typeof value === 'function'?value.bind(target):value;
}
});
proxy.set('name','Pravin');
console.log(proxy.get('name')); //Pravin (works!)
Now it works fine, because get trap binds function properties, such as map.set, to the target object (map) itself. So the value of this inside proxy.set(...) will be not proxy, but the original map. So when the internal implementation of set tries to access this.[[MapData]] internal slot, it succeeds.

Proxy aplly for method dont't call

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

Using Proxy.apply() on Node.js does not work. Is this a bug or am I doing it wrong?

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)

Categories