JS tip: Create debug friendly unique references using String

Nicolas Lepage - Jan 14 '20 - - Dev Community

Recently I've been facing a case where I needed to create some unique references.
I was creating a JS API and needed to maintain some internal state relative to elements created by the API.

In practice, I had a WeakMap where I maintained a private state for each public reference returned by the API.

Why a WeakMap?

The keys of a WeakMap are weak references, which means as soon as no one else holds the reference of a key, the key and its value get removed from the map and garbage collected.

The type of the public references didn't matter to me, I just needed unique references.
But it would be better if those references were easy to debug, which meant having at least a relevant toString() method.

⛔ Non choices ⛔

{}

What's the easiest way to create a unique reference in JS? Initializing an empty object!
But that doesn't satistfy the "easy to debug" requirement:

const ref = {}
console.log(`debug: ${ref}`)

// Output:
// debug: [object Object]

String

A string is easy to debug, you just print it:

let nextId = 1

const ref = `ref #${nextId++}`
console.log(`debug: ${ref}`)

// Output:
// debug: ref #1

But a string isn't a unique reference, anyone may obtain the same reference very easily.
And as a matter of fact, a WeakMap doesn't accept strings as keys, neither any other primitive type!

Symbol

The purpose of Symbol is to create unique references!
There I thought I had my solution...

let nextId = 1

const ref = Symbol(`ref #${nextId++}`)
console.log(`debug: ${ref}`)

// Output:
// TypeError: Cannot convert a Symbol value to a string

Wow! Far from what I expected...
Even though Symbol has a toString() method, putting it in a string interpolation yells at me!
Furthermore symbols are primitive values, hence WeakMap won't accept these as keys!

🟢 Solutions 🟢

class

There I found myself forced to write my own class:

let nextId = 1
class Ref {
  constructor() { this.id = nextId++ }
  toString() { return `ref #${this.id}` }
}

const ref = new Ref()
console.log(`debug: ${ref}`)

// Output:
// debug: ref #1

This is exactly what I wanted, but it feels like I'm squashing a fly with a sledgehammer...
Do I really have to create my own type?

new String()

Yes it's possible to use the String constructor, and it is equivalent to using a class:

let nextId = 1

const ref = new String(`ref #${nextId++}`)
console.log(`debug: ${ref}`)

// Output:
// debug: ref #1

It does create a unique reference, which isn't of a primitive type.
Hence it may be used as a key for a WeakMap!

Your turn!

Do you have any other ideas? Share them with me!

Thanks for reading, give a ❤️, leave a comment 💬, and follow me to get notified of my next posts.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .