Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to change a Proxy's target?

I have a class that implements the XMLHttpRequest interface. Depending on the URL passed to open(), I can determine whether to use the default XMLHttpRequest or my custom implementation. My idea is to use a proxy to do this:

let xhr = new XHRProxy();
xhr.open('GET', 'http://blah'); // Decide here depending on URL

I did some tests using the ES6 Proxy, which seems promising, but unfortunately the proxy target cannot be modified after constructing the Proxy:

var foo = {
    name() {
        return "foo";
    }
};
var bar = {
    name() {
        return "bar";
    }
}
var handler = {
    get(target, property, receiver) {
        if (property === "switchToBar") {
            // FIXME: This doesn't work because a Proxy's target is not exposed AFAIK
            receiver.target = bar;
            return function() {};
        } else {
            return target[property];
        }
    }
}
var proxy = new Proxy(foo, handler);
console.log(proxy.name()); // foo
proxy.switchToBar();
console.log(proxy.name()); // foo  :(

I think I can accomplish what I want by not setting a target at all - instead defining all traps to delegate to the desired object - but I'm hoping for a simpler solution.

like image 500
Peter Tseng Avatar asked Jul 14 '16 01:07

Peter Tseng


People also ask

What does a proxy do to the target object?

The Proxy object allows you to create an object that can be used in place of the original object, but which may redefine fundamental Object operations like getting, setting, and defining properties.

What is a proxy handler?

The proxy handler inspects incoming requests for the custom request headers through which the proxy server conveys the information about the original client request, and makes this information available to the web application on the Application Server using standard ServletRequest APIs.

What is proxy array?

Proxy arrays for distributed caching enable multiple proxies to serve as a single cache. Each proxy in the array will contain different cached URLs that can be retrieved by a browser or downstream proxy server.

What is proxy in console?

a Proxy is an object that encases another object or function and allows you to intercept it. Proxies are stealthy "wrapper" objects which can intercept not just write events to a target (i.e. object mutations/changes) but even read events as well (i.e. merely reading a property value).


2 Answers

Here's a go at "defining all traps to delegate to desired object"

(function () {
  let mutableTarget;
  let mutableHandler;

  function setTarget(target) {
    if (!(target instanceof Object)) {
      throw new Error(`Target "${target}" is not an object`);
    }
    mutableTarget = target;
  }

  function setHandler(handler) {
    Object.keys(handler).forEach(key => {
      const value = handler[key];

      if (typeof value !== 'function') {
        throw new Error(`Trap "${key}: ${value}" is not a function`);
      }

      if (!Reflect[key]) {
        throw new Error(`Trap "${key}: ${value}" is not a valid trap`);
      }
    });
    mutableHandler = handler;
  }

  function mutableProxyFactory() {
    setTarget(() => {});
    setHandler(Reflect);

    // Dynamically forward all the traps to the associated methods on the mutable handler
    const handler = new Proxy({}, {
      get(target, property) {
        return (...args) => mutableHandler[property].apply(null, [mutableTarget, ...args.slice(1)]);
      }
    });

    return {
      setTarget,
      setHandler,
      getTarget() {
        return mutableTarget;
      },
      getHandler() {
        return mutableHandler;
      },
      proxy: new Proxy(mutableTarget, handler)
    };
  }

  window.mutableProxyFactory = mutableProxyFactory;
})();

const { 
  proxy, 
  setTarget 
} = mutableProxyFactory();

setTarget(() => 0);
console.log(`returns: ${proxy()}`);

setTarget({ val: 1 });
console.log(`val is: ${proxy.val}`);

setTarget({ val: 2 });
console.log(`val is: ${proxy.val}`);

setTarget(() => 3);
console.log(`returns: ${proxy()}`);

I feel like there must be some reason this isn't supported out of the box, but I don't have enough information to comment on that further.

After hacking on this for a while, I've observed a few things. It seems the original target that the proxy constructor is called with is treated as part of the proxy's identity regardless. Setting the original target to a plain object and swapping the target to a function later raises an error, when the proxy is called. There are definitely some rough edges to this, so use with caution.

like image 161
John Griffing Avatar answered Oct 13 '22 08:10

John Griffing


Is it possible to change a Proxy's target?

No, this is not possible. The proxy handler is a quite generic interface already, and by defining all traps to forward the operation to a different handler this is easily achievable. That's why there is no extra method to change the target, the interface is kept minimal. By not making the target changeable, also the shape of the proxy is preserved (e.g. whether it's callable or an array).

like image 22
Bergi Avatar answered Oct 13 '22 07:10

Bergi