Notification proxies (Was: possible excessive proxy invariants for Object.keys/etc??)

Tom Van Cutsem at
Sun Dec 2 11:40:10 PST 2012

2012/12/2 Mark S. Miller <erights at>

> I think we can rescue around wrapping. I'll call this approach "opaque
> around wrapping". The idea is that the proxy passes into the handler
> trap a proxy-generated no-argument function (a thunk) called "action".

Interesting. I had thought of a similar approach, but hadn't pursued it
because I thought allocating a thunk per trap invocation would be deemed
too expensive. I'll go ahead and sketch the design, which I think
corresponds closely with your approach B)

First, from the point-of-view of the trap implementor, things would work as
follows (I show the "defineProperty" trap only as an example)

All traps take an extra thunk as last argument, which I will name
"forward", since calling that thunk has the effect of forwarding the
operation to the target, returning the original result.

defineProperty: function(target, name, desc, forward) {
  // before
  var result = forward();
  // after
  return result;

Now, from the point-of-view of the proxy implementation, how is the above
trap called?

// in the proxy, intercepting Object.defineProperty(proxy, name, desc)
// where proxy = Proxy(target, handler)
var result = Uninitialized;
var thunk = function() {
  if (result === Disabled) throw new Error("can only call forward() during
trap invocation");
  if (result !== Uninitialized) throw new Error("forward() can be called
only once");
  result = Reflect.defineProperty(target, name, desc); // forward to target
  return result;
var trapResult = handler["defineProperty"].call(handler, target, name,
desc, thunk);
if (result !== Uninitialized && result === trapResult) return result; // no
invariant checks when original result is returned
if (result === Uninitialized || result !== trapResult) { /* do invariant
checks */ ; return result; }
result = Disabled; // ensures one cannot call forward() outside of dynamic
extent of the invocation

I'll answer your questions for the above design:

> Open questions that take us to different design possibilities:
> 1) what happens if the trap does not call action?
Invariant checks are performed on the returned result.

> 2) what happens if the trap calls action more than once?
An exception is thrown.

> 3) do actions ever return anything, in case the trap wants to pay
> attention to it?
Yes, it returns the value of the operation, applied to the target.

> 4) are there any return values from the trap that the proxy would pay
> attention to?
Yes. If the trap returns the original result, it doesn't do any extra
checks. Otherwise it does.

> 5) if the original operation performed by action throws, should the
> action still just return to the trap normally?
A thrown exception would escape and abort the entire operation, unless the
trap wraps the call to forward() in a try-catch block.

> 6) what happens if the trap throws rather that returning?
Thrown exceptions are always propagated to clients.

> I prefer #A to #B as it gains the full benefits of simplifying the
> overall spec and implementation. However, #B still seems better than
> current direct proxies, as the normal case gets target-maintenance and
> invariant checking for free.

I agree that #B doesn't really simplify anything in the spec (the invariant
checks are still there sometimes).
I'm not sure if it is inherently better than the current design though: we
gain performance in terms of avoiding invariant checks, but we lose
performance by having to allocate a per-call thunk, + the API becomes more
complex (an additional parameter to every trap)

I do think that passing an action or "forward()" thunk to a trap as extra
argument beats David's proposed "throw ForwardToTarget" trick in terms of
elegance and usability (maybe not in terms of performance).

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <>

More information about the es-discuss mailing list