Why is Proxy to a Map object in ES2015 not working

The reason you’re getting the error is that the proxy isn’t getting involved in the p1.set() method call (other than that the get trap is used to retrieve the function reference). So once the function reference has been retrieved, it’s called with this set to the proxy p1, not the map m1 — which the set method of a Map doesn’t like.

If you’re really trying to intercept all property access calls on the Map, you can fix it by binding any function references you’re returning from get (see the *** lines):

const loggingProxyHandler = {
    get(target, name/*, receiver*/) {
        let ret = Reflect.get(target, name);
        console.log(`get(${name}=${ret})`);
        if (typeof ret === "function") {    // ***
          ret = ret.bind(target);           // ***
        }                                   // ***
        return ret;
     },

     set(target, name, value/*, receiver*/) {
         console.log(`set(${name}=${value})`);
         return Reflect.set(target, name, value);
     }
};

function onRunTest() {
    const m1 = new Map();
    const p1 = new Proxy(m1, loggingProxyHandler);
    p1.set("a", "aval");
    console.log(p1.get("a")); // "aval"
    console.log(p1.size);     // 1
}

onRunTest();
NOTE: Requires a browser supporting ES2015's Proxy

Notice that when calling Reflect.get and Reflect.set, we don’t pass along the receiver (in fact, we’re not using the receiver argument at all in those, so I’ve commented the parameter out). That means they’ll use the target itself as the receiver, which you need if the properties are accessors (like Map‘s size property) and they need their this to be the actual instance (as Map‘s size does).


If your goal is just to intercept Map#get and Map#set, though, you don’t need a proxy at all. Either:

  1. Create a Map subclass and instantiate that. Assumes you control the creation of the Map instance, though.

  2. Create a new object that inherits from the Map instance, and override get and set; you don’t have to be in control of the original Map‘s creation.

  3. Replace the set and get methods on the Map instance with your own versions.

Here’s #1:

class MyMap extends Map {
  set(...args) {
    console.log("set called");
    return super.set(...args);
  }
  get(...args) {
    console.log("get called");
    return super.get(...args);
  }
}

const m1 = new MyMap();
m1.set("a", "aval");
console.log(m1.get("a"));

#2:

const m1 = new Map();
const p1 = Object.create(m1, {
  set: {
    value: function(...args) {
      console.log("set called");
      return m1.set(...args);
    }
  },
  get: {
    value: function(...args) {
      console.log("get called");
      return m1.get(...args);
    }
  }
});

p1.set("a", "aval");
console.log(p1.get("a"));

#3:

const m1 = new Map();
const m1set = m1.set; // Yes, we know these are `Map.prototype.set` and
const m1get = m1.get; // `get`, but in the generic case, we don't necessarily
m1.set = function(...args) {
  console.log("set called");
  return m1set.apply(m1, args);
};
m1.get = function(...args) {
  console.log("get called");
  return m1get.apply(m1, args);
}

m1.set("a", "aval");
console.log(m1.get("a"));

Leave a Comment