Harmony Proxies: Tutorial

Harmony Proxies: Tutorial

The Proxy API enables the creation of dynamic proxies in Javascript. Dynamic proxies are useful for writing generic object or function wrappers or for creating virtual object abstractions. To avoid any confusion: these proxies have nothing to do with web proxy servers.

Perhaps the most compelling use of proxies is the ability to create objects whose properties can be computed dynamically. Proxies encompass all use cases that one would use the SpiderMonkey hook __noSuchMethod__ for (such as creating mock objects in testing frameworks, Rails ActiveRecord dynamic finder-like capabilities, etc.) but generalize to operations other than method invocation.

What platforms support Proxies?

The Proxy API will be part of ECMAScript 6. To see whether proxies are supported in your browser, check for the presence of a global object named Proxy. At the time of writing (August 2012), Firefox, Chrome and node.js support an older version of the API (for which you can find an older version of this tutorial here and MDN docs here). In Chrome, proxies are hidden behind an "experimental Javascript" flag that can be set by visiting chrome://flags. In node.js, use the node --harmony flag.

Proxies underwent several revisions as they were being prototyped by browser engine implementors. This tutorial discusses the newer, so-called "Direct Proxies" API. At the time of writing (August 2012), no built-in support for this version of the API yet exists in browsers. However, there exists a shim that implements the new API on top of the old API. After loading this reflect.js script, you should be able to run the examples on this page.

Hello, Proxy!

The following piece of code creates a proxy that intercepts property access and returns for each property "prop", the value "Hello, prop":

var p = Proxy({}, {
  get: function(target, name) {
    return 'Hello, '+ name;
  }
});

document.write(p.World); // should print 'Hello, World'

Try it in your browser.

The call Proxy(target, handler) returns a new proxy object that wraps an existing target object. All property accesses performed on this proxy will be interpreted by calling the handler.get method. That is, the code p.foo is interpreted by the VM as if executing the code handler.get(target,"foo") instead, where p = Proxy(target, handler).

Property access is not the only operation that can be intercepted by proxies. The following table lists the most important operations that can be performed on a proxy:

Operation Intercepted as
proxy[name] handler.get(target, name)
proxy[name] = val handler.set(target, name, val)
name in proxy handler.has(target, name)
delete proxy[name] handler.deleteProperty(target, name)
for (var name in proxy) {...} handler.enumerate(target).forEach(function (name){...})
Object.keys(proxy) handler.keys(target)

Methods on the handler object such as get, set etc. are called traps.

The full proxy handler API additionally traps calls to newer ECMAScript 5 built-in methods such as Object.keys and Object.getOwnPropertyDescriptor. Fortunately it is not always necessary to implement the full API to build useful proxies, as demonstrated in the following sections.

A simple profiler

Let's construct a simple kind of generic wrapper: a profiler that counts the number of times each of its properties was accessed:

function makeSimpleProfiler(target) {
  var count = Object.create(null);
  return {
    proxy: Proxy(target, {
      get: function(target, name) {
        count[name] = (count[name] || 0) + 1;
        return Reflect.get(target, name);
      }
    }),
    get stats() { return count; }
  };
}

The function makeSimpleProfiler takes as its sole argument the object we want to monitor. It returns a tuple t such that t.proxy refers to the wrapper that will record profiling data, and t.stats can be used to retrieve the profiling information recorded thus far. We can use this abstraction as follows:

var subject = { foo: 42, bar: 24 };
var profiler = makeSimpleProfiler(subject);
runApp(profiler.proxy);
plotHistogram(profiler.stats);

Try it in your browser.

Note that the proxy handler only implements a single trap, the get trap. What happens if we apply operations on the proxy object for which no corresponding trap was defined, such as "foo" in profiler.proxy? In such cases, the proxy will simply automatically forward the operation to the target object, unmodified. The result would be the same as if we had written "foo" in subject.

There is also a way for proxy traps to explicitly forward an operation to the target object. The function Reflect.get on line 7 of makeSimpleProfiler allows the proxy to forward the operation that it intercepted (in this case, a get operation) to the target object. In this particular case, Reflect.get(target, name) is more or less equivalent to target[name]. There exist similar such Reflect forwarding functions for all available traps. For instance, Reflect.has(target, name) can be used to forward an intercepted has operation, and returns whether or not the target object has the name property.

The Reflect global object is made available by the reflect.js shim. In ECMAScript 6, the Reflect object will probably become a proper module.

Emulating noSuchMethod

Using the Proxy API, it's fairly straightforward to emulate the functionality of SpiderMonkey/Rhino's __noSuchMethod__ hook in a browser that does not support it natively (e.g. Chrome/v8). All that is required is that the object eventually delegate to a special proxy object that will trap missing invocations:

function MyObject() {};
MyObject.prototype = Object.create(NoSuchMethodTrap);
MyObject.prototype.__noSuchMethod__ = function(methodName, args) {
  return 'Hello, '+ methodName;
};

new MyObject().foo() // returns 'Hello, foo'

Below is the code for the NoSuchMethodTrap root object. The get handler method short-circuits invocations of __noSuchMethod__. If it wouldn't do this, a missing method invocation on an object that didn't implement __noSuchMethod__ would recursively trigger the hook, leading to an infinite recursion.

var NoSuchMethodTrap = Proxy({}, {
  get: function(target, name) {
    if (name === '__noSuchMethod__') {
      throw new Error("receiver does not implement __noSuchMethod__ hook");
    } else {
      return function() {
        var args = Array.prototype.slice.call(arguments);
        return this.__noSuchMethod__(name, args);
      }
    }
  }
});

Try it in your browser.

Contrary to the previous example (the profiler), this example illustrates that it's not always necessary for proxies to make good use of their "target" object. In this example, the special "no such method" proxy just uses an empty object as the target, and ignores the target parameter of the get trap.

Remote objects

Proxies allow you to create virtual objects that represent remote or persisted objects. To demonstrate this, let's write a wrapper around an existing library for remote object communication in Javascript. Tyler Close's web_send library can be used to construct remote references to server-side objects. One can then "invoke" methods on this remote reference via HTTP POST requests. Unfortunately, remote references cannot be used as objects themselves. That is, to send a message to a remote reference ref, one writes:

Q.post(ref, 'foo', [a,b,c]);

It would be a lot more natural if one could instead treat ref as if it were an object, such that one could call its methods as ref.foo(a,b,c). Using the Proxy API, we can write a wrapper for remote web_send objects to translate property access into Q.post calls:

function Obj(ref) {
  return Proxy({}, {
    get: function(target, name) {
      return function() {
        var args = Array.prototype.slice.call(arguments);
        return Q.post(ref, name, args);
      };
    }
  });
}

Now one can use the Obj function to write Obj(ref).foo(a,b,c).

Higher-order Messages

A higher-order message is a message that takes another message as its argument, as explained in this paper. Higher-order messages are similar to higher-order functions, but are sometimes more succinct to write. Using the Proxy API, it's straightforward to build higher-order messages. Consider a special object named _ that turns messages into functions:

var msg = _.foo(1,2)
msg.selector; // "foo"
msg.args; // [1,2]
msg(x); // x.foo(1,2)

Note that msg is a function of one argument, as if defined as function(r) { return r.foo(1,2); }. The following example is a direct translation from the aforementioned paper and shows how higher-order messages lead to shorter code:

var words = "higher order messages are fun and short".split(" ");
String.prototype.longerThan = function(i) { return this.length > i; };
// use HOM to filter and map based on messages rather than functions
document.write(words.filter(_.longerThan(4)).map(_.toUpperCase()));
// without HOM, this would be:
// words.filter(function (s) { return s.longerThan(4) })
//       .map(function (s) { return s.toUpperCase() })

Finally, here is the code for the _ object:

// turns messages into functions
var _ = Proxy({}, {
  get: function(target, name) {
    return function() {
      var args = Array.prototype.slice.call(arguments);
      var f = function(rcvr) {
        return rcvr[name].apply(rcvr, args);
      };
      f.selector = name;
      f.args = args;
      return f;
    }
  }
});

Try it in your browser.

Emulating host objects

Proxies give Javascript programmers the ability to emulate much of the weirdness of host objects, such as the DOM. For instance, using proxies it is possible to emulate "live" nodelists. Thus, proxies are a useful building block for self-hosted Javascript DOM implementations, such as dom.js.

Function Proxies

It's also possible to create proxies for function objects. In Javascript, functions are really objects with two extra capabilities: they can be called (as in f(1,2,3)), and constructed (as in new f(1,2,3)). To intercept those operations, all you need to do is define the following two additional traps:

Operation Intercepted as
proxy(a,b,c) handler.apply(target, undefined, [a,b,c])
new proxy(a,b,c) handler.construct(target, [a,b,c])

The second argument to the apply trap is actually the "this"-binding of the function. When a proxy is called like a normal, stand-alone function, this parameter will be set to undefined (not to the global object!). When a proxy for a function is called as a method, as in obj.proxy(a,b,c), then this second argument will be set to obj. Here is a simple example of function proxies in action:

var handler = {
  get: function(target, name) {
    // can intercept access to the 'prototype' of the function
    if (name === 'prototype') return Object.prototype;
    return 'Hello, '+ name;
  },
  apply: function(target, thisBinding, args) { return args[0]; },
  construct: function(target, args) { return args[1]; }
};
var fproxy = Proxy(function(x,y) { return x+y; },  handler);

fproxy(1,2); // 1
new fproxy(1,2); // 2
fproxy.prototype; // Object.prototype
fproxy.foo; // 'Hello, foo'

Try it in your browser.

Note that, because function calling and function construction are trapped by different traps, you can use function proxies to reliably detect whether or not your function was called using the new keyword.

Function proxies also enable some other idioms that were previously hard to accomplish in pure JS. Thanks to Dave Herman for pointing these out:

First, function proxies make it simple to wrap any non-callable target object as a callable object:

function makeCallable(target, call) {
  return Proxy(target, {
    apply: function(target, thisBinding, args) {
      return call.apply(thisBinding, args);
    }
  });
}

Second, function proxies also make it simple to create pseudo-classes whose instances are callable:

function Thing() {
  /* initialize state, etc */
  return makeCallable(this, function() {
    /* actions to perform when instance
       is called like a function */
  });
}

Experimenting with new semantics

For the language buffs: Proxies can be used to create an implementation of 'standard' Javascript objects and functions in Javascript itself. Once the semantics of Javascript objects is written down in Javascript itself, it becomes easy to make small changes to the language semantics, and to experiment with those changes. For instance, I prototyped a proposed feature for observing objects in Javascript itself, using proxies. As another example, David Bruant implemented Javascript Arrays in Javascript itself using Proxies. Bob Nystrom illustrates how Proxies could be used to augment Javascript objects with multiple inheritance.

Proxy Tips

Avoiding runaway recursion

The "get" and "set" traps take as an optional last argument the "initial receiver" of the property access/assignment. In cases where a property is accessed on a proxy directly, this "initial receiver" will be the proxy object itself. Touching the proxy object from within a trap is dangerous as it may easily lead to runaway recursion. This is especially nasty if you think you aren't touching the proxy, but some implicit conversion (e.g. toString conversion) does cause a message to be sent to the proxy. For example, printing the receiver for debugging will send a toString() message to it, which may lead to infinite recursion. Say we have a simple proxy handler that just forwards all operations to a target object. For debugging purposes, we'd like to know the initial receiver of a property access:

get: function(target, name, receiver) {
  print(receiver);
  return target[name];
}

If p is a proxy with the above get trap, then p.foo will go into an infinite loop: first the get trap is called with name="foo", which prints receiver (that is bound to p). This in turn calls p.toString(), which calls the get trap again, this time with name="toString". Again, receiver is bound to p, so printing it again will cause another toString() invocation, and so on.

Proxies as handlers

Proxy handlers can be proxies themselves. The Proxy Tracer below demonstrates how this pattern can be used to "funnel" all operations applied to a proxy handler through a single get trap.

Proxy Tracer/Probe

A tracer proxy (view source) that simply prints a description of all operations performed on itself. Useful for debugging, or for learning how operations are intercepted by proxy handlers.

Try it in your browser.

A probe proxy (view source) that, like the tracer above, logs all meta-level operations applied to it, but that is more configurable (the default logger prints the operations, but you can provide your own logger).

Try it in your browser.

More Examples

See the documentation of the harmony-reflect project.

Note: the below examples are all written using the old, deprecated Proxy API.

  • A generic object tracer [code]: simply logs all operations performed on an object via the tracer proxy.
  • A revocable reference [code]: a proxy that forwards all operations to a target object until revoked. After it is revoked, the proxy can no longer be used to affect the target object. Useful if an object only requires temporary access to another object.
  • A revocable reference with funneling [code]. Funneling uses a proxy as a handler so that all operations trap a single "get" trap.
  • A generic membrane [code]: a membrane transitively wraps all objects that enter or exit the membrane. When the membrane is revoked, the entire object graph of objects wrapped inside the membrane is disconnected from objects outside of the membrane. Membranes are the basis of security wrappers and can be used to isolate untrusted code.

Further Reading

The following is a list of dated resources that mostly deal with an old version of the Proxy API. They are very useful documents for context, but not for learning the details of how to use the current Proxy API:

  • The first ECMAScript Harmony proposal (now deprecated by the current direct proxies API).
  • The Mozilla Developer Network Proxy documentation.
  • A conformance test suite for the specification is available and can be run using a Javascript shell or in a browser.
  • Design background: first half of this Google Tech Talk and the following academic paper, presented at DLS 2010.
  • Brendan Eich, on his blog, concisely explains some of the rationale behind Proxies.
  • Sebastian Markb√•ge, on his blog, explains the "leaky this" problem: proxies are often used to wrap objects, but the wrapped object may "leak" outside of its wrapper in some cases.
  • Frameworks making use of proxies:
    • dom.js, a self-hosted JavaScript implementation of a WebIDL-compliant HTML5 DOM.
    • mdv, model-driven views.