2012/12/2 Mark S. Miller <erig...@google.com> > 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). Cheers, Tom
_______________________________________________ es-discuss mailing list es-discuss@mozilla.org https://mail.mozilla.org/listinfo/es-discuss