1

I'm creating a virtualized object that lazily builds its properties with a rules engine as a technique to skip calculating values that are never read. For this, I'm using a Proxy. It seems like proxies sort of serve double-duty for both access forwarding and virtualizing; in my case, I mostly care about the latter, not the former.

The problem I'm having has to do with trying to implement the getOwnPropertyDescriptor trap in the proxy. When doing so I get the error:

TypeError: 'getOwnPropertyDescriptor' on proxy: trap returned descriptor for property 'foo' that is incompatible with the existing property in the proxy target

Because I'm not actually forwarding requests to a wrapped object, I've been using an Object.freeze({}) as the first argument passed to new Proxy(...).

function createLazyRuleEvaluator(rulesEngine, settings) {
  const dummyObj = Object.freeze({});
  Object.preventExtensions(dummyObj);

  const valueCache = new Map();

  const handlers = {
    get(baseValue, prop, target) {
      if (valueCache.has(prop)) 
        return valueCache.get(prop);

      const value = rulesEngine.resolveValue(settings, prop);
      valueCache.set(prop, value);
      return value;
    },

    getOwnPropertyDescriptor(baseValue, prop) {
      return !propIsInSettings(prop, settings) ? undefined : {
        configurable: false,
        enumerable: true,
        get() {
          return handlers.get(baseValue, prop, null);
        }
      };
    },
  };

  return new Proxy(dummyObj, handlers);
}

// This throws
Object.getOwnPropertyDescriptor(createLazyRuleEvaluator(/*...*/), 'foo');

The proxy object is meant to resemble a object with read-only properties that can't be extended or have any other such fanciness. I tried using a frozen and non-extensible object, but I'm still told the property descriptor is incompatible.

When I try using a non-frozen object as the proxy target, then I get a different type error:

TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property 'foo' which is either non-existent or configurable in the proxy target

What am I doing wrong here? Is there any way I can have my proxy exhibit non-configurability?

2
  • 1
    "I'm not actually forwarding requests to a wrapped object" - yes you are. All the requests that you didn't write a trap handler for are still forwarded to the object, like has or set. Commented Dec 10, 2019 at 21:32
  • No, I'm not. The code as posted is not complete (I'm using TDD and hit this snag). I'll be implementing all the trap handlers. Commented Dec 10, 2019 at 21:36

1 Answer 1

2

You might want to consult the list of invariants for handler.getOwnPropertyDescriptor(), which unfortunately includes these:

  • A property cannot be reported as existent, if it does not exist as an own property of the target object and the target object is not extensible.
  • A property cannot be reported as non-configurable, if it does not exist as an own property of the target object or if it exists as a configurable own property of the target object.

Since your target is a frozen, empty object, the only valid return value for your handler.getOwnPropertyDescriptor() trap is undefined.


To address the underlying question though, lazily initialize your property descriptors when they're attempted to be accessed:

function createLazyRuleEvaluator(rulesEngine, settings) {
  const dummyObj = Object.create(null);
  const valueCache = new Map();
  const handlers = {
    get(target, prop) {
      if (valueCache.has(prop)) 
        return valueCache.get(prop);

      const value = prop + '_value'; // rulesEngine.resolveValue(settings, prop)
      valueCache.set(prop, value);
      return value;
    },
    getOwnPropertyDescriptor(target, prop) {
      const descriptor =
        Reflect.getOwnPropertyDescriptor(target, prop) ||
        { value: handlers.get(target, prop) };

      Object.defineProperty(target, prop, descriptor);
      return descriptor;
    },
  };

  return new Proxy(dummyObj, handlers);
}

console.log(Object.getOwnPropertyDescriptor(createLazyRuleEvaluator(/*...*/), 'foo'));

You can follow this pattern for each of the method traps to preempt unwanted mutations to your dummyObj by lazily initializing any properties accessed.

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

12 Comments

So does this mean it's impossible to create a fully virtual proxy that presents as non-extensible?
@Jacob that's not necessarily true. Give me a few minutes to try out an idea and I'll update my answer. It might be possible to lazily define properties on the target object before returning from getOwnPropertyDescriptor() so that the invariants are not technically violated.
@Jacob before I do that though, could you update your question with an example implementation of handler.get()? My edit will be much more helpful with that context.
Sure, done (hopefully is enough to get this across).
If I skipped the reflection methods that would make things simpler, but then reflection methods would return invalid results. I want to make sure this object looks as accurate as possible during reflection.
|

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.