Setting up symbol-valued constants via proxies

[2022-01-26] dev, javascript, proxy
(Ad, please don’t block)

Dean Tribble (via Rob Palmer) has come up with a neat trick to create constants whose values are strings with their names. With a minor change, we can use it to set up symbol-valued constants.

Setting up constants  

const SymbolFactory = new Proxy({}, {
  get: (_target, propKey, _receiver) => Symbol(propKey) // (A)
});

const {red, green, blue} = SymbolFactory; // (B)
assert.equal(
  typeof red, 'symbol'
);
assert.equal(
  String(red), 'Symbol(red)'
);

How does it work?  

Each property of the pattern that object-destructures SymbolFactory, triggers a property get operation. Therefore, line B is equivalent to:

const red = SymbolFactory.red;
const green = SymbolFactory.green;
const blue = SymbolFactory.blue;

The method in line A intercepts each of these get operations. Among other information, it is provided with the key of the relevant property. It then creates and returns a symbol whose description is the property key.

Wouldn’t it be better to cache the results?  

You might think that we should cache the results so that every time we use a name, we get the same symbol. We can do that via a Map (we can’t use a WeakMap because its keys must be objects):

const cache = new Map();
const SymbolFactory = new Proxy({}, {
  get: (_target, propKey, _receiver) => {
    let result = cache.get(propKey);
    if (result === undefined) {
      result = Symbol(propKey);
      cache.set(propKey, result);
    }
    return result;
  }
});

Or we can use Symbol.for():

const SymbolFactory = new Proxy({}, {
  get: (_target, propKey, _receiver) => Symbol.for(propKey)
});

However, each invocation producing a fresh symbol is a feature, not a bug – for example:

// colors.mjs
const {red, green, blue} = SymbolFactory;

// mood.mjs
const {happy, blue} = SymbolFactory;

We want each blue to be unique!

We could cache and always access factory results via properties of SymbolFactory, but then we aren’t protected against typos and basically get one big enum.

Further reading