I have an ES6 Proxy which contains other deeply nested Proxies (generated in the get trap of the root proxy). All of them use the same trap handler.
When I try to get the value of a deeply nested object in this collection, the getter is called for every deeply nested object encountered. It is imperative that a record of this so-called 'path' through the object is known after the sequence of gets is complete. For example, calling:
console.log(proxy.shallow.deep);
is a 'chain' of two get operations, on shallow
and deep
in sequence, both of which are pushed to an array acting as a breadcrumb trail of properties accessed in the chain, like:
console.log(path); // ['shallow', 'deep']
There's a problem with this, though. In any sequence of gets, it's not possible to know what the final get in the chain is - every get to the proxy is treated as an atomic action so there is no intrinsic notion of what property is the last to be called. This means in subsequent 'get chains', the path
array already has values associated with it. I've written some code below to illustrate the structure of my proxy and the problem I'm encountering:
let nestedObject = { shallow: { deep: 0 }};
let path = [];
let proxy = new Proxy(nestedObject, {
get(target, prop) {
path.push(prop);
let out = target[prop];
// this ensures each get to the proxy (whenever the value at property
// is an object), returns that object as a new proxy,
// else return the value of the property
// this means the get/set traps are called for each deeply nested object
return typeof out === 'object' ? new Proxy(out, this) : out;
},
set(target, prop, val) {
path.push(prop);
target[prop] = val;
path.length = 0;
}
});
// getting these two pushes both properties to the path array
// but there's no way of knowing that the get to 'deep' was the final
// get in the chain - therefore it cannot be reset for the next get chain
proxy.shallow.deep;
// path: ['shallow', 'deep']
console.log(path);
// because of this, I can't figure out how the code should know that the path
// array must be reset - so subsequent gets just add more values to the path array,
// and there are erroneous elements in the array when the next chain starts
proxy.shallow.deep
// path: ['shallow', 'deep', 'shallow', 'deep']
console.log(path);
// setting a value is fine however, since a set action will always be the last action
// it pushes 'shallow' to the array in the getter and 'deep' in setter.
// The setter can then clear the path array.
proxy.shallow.deep = 1;
// path: []
console.log(path);
So what I'm wondering is: Is it possible to know what the last 'get' is in the chain somehow, and/or if there's a way to perhaps call a function whenever a chain of gets has completed?
Thanks for the help!
I think I've found a reasonable solution for me and it involves very few changes to the code. It involves attaching a path variable to each successive trap handler, the value of which is the accumulated values of each previous handler's path value. Like so:
let nestedObject = { shallow: { deep: {deepest: 0 }}};
let path = [];
let proxy = new Proxy(nestedObject, {
get(target, prop) {
let out = target[prop];
// make a copy of this handler for the nested proxy to use
let copy = Object.assign({}, this);
// if this handler contains no path variable, add it
// otherwise concatenate it with the previous handler's path
!this.path ?
Object.assign(copy, {path: [prop]}) :
Object.assign(copy, {path: this.path.concat(prop)});
path = copy.path;
return typeof out === 'object' ?
new Proxy(out, copy) :
out;
},
set(target, prop, val) {
path = this.path.concat(prop);
target[prop] = val;
}
});
proxy.shallow;
console.log(path); // ['shallow']
proxy.shallow.deep;
console.log(path); // ['shallow', 'deep']
proxy.shallow.deep.deepest;
console.log(path); // ['shallow', 'deep', 'deepest']
proxy.shallow.deep.deepest = 1;
console.log(path); // ['shallow', 'deep', 'deepest']
I'm not sure if this is the optimum solution, but it works. Though I'd still love to see any alternative ways of implementing this.
Thanks for the help guys.