0

When I use Set and Map objects in javascript, I chronically say the wrong thing:

  • set.length when I mean set.size
  • map.length when I mean map.size
  • map.someKeyName or map['someKeyName'] when I mean map.get('someKeyName')
  • map.someKeyName=someValue or map['someKeyName']=someValue when I mean map.set('someKeyName', someValue)

The result is a passive-aggressive undefined or silently failing to do what I wanted, which wastes my time.

How hard would it be to make some kind of slightly-altered version of Set and Map that throw an Error when I try to do any of those bad things?

Don Hatch
  • 5,041
  • 3
  • 31
  • 48
  • I'm guessing you would have to extend the object types with the extra properties – Pete May 05 '17 at 09:44
  • @Pete What extra properties? Wait, are you thinking I want them to understand those wrong names? Noooooooooo, I want them to throw an Error when I use them! – Don Hatch May 05 '17 at 09:51
  • if you map them as extra properties, you can make those properties throw an exception when used - it's the only way you would be able to get them to slap you in the face so to speak, but without extending the initial object, you can't access those propeties and they will always be undefined – Pete May 05 '17 at 09:54
  • @Pete Ah, I see. Okay, I think that would cover `length`. What about brackets? – Don Hatch May 05 '17 at 09:56
  • Not too sure about that - don't know enough js! – Pete May 05 '17 at 09:59

1 Answers1

1

Here's something that seems to do exactly what I want (tested on Chrome 59). Most of it is thanks to @T.J.Crowder 's answer here ; I had to add a special case for the size property for some reason.

Caveat: MDN says Map has a length property that's always 0. I think it's lying; or, at least, it's wrong on Chrome 59 where I'm testing it, so when I access the length property on my proxy object it fails with "No length for you!" as desired. It would be probably be safer to special case it to guarantee that it fails as desired regardless of whether the underlying Map has a length.

// AggressiveMap ctor.
// Like Map but only allows getting properties that exist,
// and doesn't allow setting properties at all.
const AggressiveMap = function(arg) {
  return new Proxy(new Map(arg), {
    // Property getter
    get: function(realThing, name, receiver) {
      if (!(name in realThing)) {
        throw new Error("No "+name+" for you!");
      }
      // No idea why this is necessary, but if I don't special case 'size', I get
      // "Uncaught TypeError: Method Map.prototype.size called on incompatible receiver [object Object]"
      // on Chrome 59.
      if (name === 'size') { return realThing.size; }

      let answer = Reflect.get(realThing, name, receiver);
      if (typeof answer === 'function') {
        // https://stackoverflow.com/questions/43236329/why-is-proxy-to-a-map-object-in-es2015-not-working/43236808#answer-43236808
        answer = answer.bind(realThing);
      }
      return answer;
    },
    // Property setter
    set: function(realThing, name, value, receiver) {
      // Don't allow setting any properties.
      throw new Error("No "+name+" for you!");
      // But here's how to do it if we wanted to allow it.
      return Reflect.set(realThing, name, value, receiver);
    },
  });
};  // AggressiveMap ctor

// Exercise it a bit
const map = AggressiveMap([['a',1],['b',2],['c',3]]);
map.set('d', 4);
map.delete('c');
console.log("map.get('b') = "+JSON.stringify(map.get('b')));
console.log("Iterating:");
for (const [key,value] of map) {
  console.log("  "+JSON.stringify(key)+" -> "+JSON.stringify(value));
}
console.log("map.size = "+JSON.stringify(map.size));
map['moose'] = 'foo';  // No moose for you!
console.log("map['moose'] = "+JSON.stringify(map['moose']));  // No moose for you!  (gets here if you comment out previous line)
NOTE: Requires a browser supporting ES2015's Proxy.
Community
  • 1
  • 1
Don Hatch
  • 5,041
  • 3
  • 31
  • 48