I have been using Flow recently to add some static-type checking to JavaScript. I'm not going to get into the details about Flow itself in this article, but rather how I've approached enums.

This is certainly not new and there are a tons of libraries out there that have various advantages and disadvantages. One great implementation that works more like a strict enum is enumify. However, without extra manual type hinting Flow and PhpStorm (my choice IDE) are unable to find errors and allow code-completion respectively.

An ideal solution would be to have both Flow static analysis and code completion in my IDE. That’s what I sought out to achieve. But let's start from the beginning...

Vanilla Javascript

If you haven't already gathered, enumerations are not natively supported by JavaScript. It's not a difficult concept and a lot of people are just fine with using values in an object like:

var Color = {
RED : 0,
GREEN : 1,
BLUE : 2
}

Advantages:

  1. Simple to understand and has been used or a long time.
  2. Requires no extra libraries.
  3. Does not need transpilers.

Disadvantages:

  1. It's not possible to type-check (something like typeof color === 'Color') since constants can be any type and do not inherently belong to a parent or collection.
  2. Code-completion works when only using the literals (like color === Color.RED) but cannot be tracked once that value is handed to a variable. This is important when you want to catch potential errors with values that are not part of the enum being used as if there were. It's almost impossible to see or catch manually.

How About enumify?

As I mentioned before enumify is a great library and possibly the most heavily used by fans of enum:

import {Enum} from 'enumify';

class Color extends Enum {}
Color.initEnum(['RED', 'GREEN', 'BLUE']);

You can read more on the features here but the take-away is that it dynamically sets up instance-variables and their respective values (in this case Color.RED === 'RED').

Advantages:

  1. It handles all the messiness of creating and assigning each of the enums and their values.
  2. No need for a transpiler.
  3. Provides an Enum superclass with methods for converting literals to enums and visa-versa safely.

Disadvantages:

  1. Does require an extra library (really not a big deal these days).
  2. No support for code-completion since your IDE will not know that RED is a property of Color.
  3. No support for Flow for the same reason. It’s also not possible to catch illegals and always false conditions like: 'RED' === Color.RED
  4. By default the value is based on the name. If the value is stored somewhere outside of the application (such as a database) and the name of the enum is changed (perfectly reasonable, that's why we abstract magic numbers with a name) then the application will potentially break functionality. This is why it's a good idea to have a auto generated unique value that would only change if the order of the enums was changed (not likely since most engineers understand the repercussions) or you can be more strict and provide values yourself.

My Attempt

I couldn't find any libraries that met my needs. This is what I've come up with:

class Color extends Enum {
static RED = new Color();
static GREEN = new Color();
static BLUE = new Color();
}

Unfortunately, as of the writing of this that syntax requires experimental features. You can avoid this by using:

class Color extends Enum {}
Color.RED = new Color();
Color.GREEN = new Color();
Color.BLUE = new Color();

This is less neat and less clear about all the encapsulated values, but JavaScript is an ever changing language so who knows what the syntax will be like in a few short years.

Each of the items will be assigned an automatic value starting from zero. Or you can provide an explicit value to the constructor (Color.RED = new Color('RED');)

Color.Blue // {"value":2}
Color.Blue == 2 // true
Color.Blue === 2 // false
Color.Blue === Color.Blue // true
Color.Blue === Color.valueOf(2) // true
Color.Blue === Color.valueOf('2') // false
'Color: ' + Color.Blue // "Color: 2"
Color.valueOf(3) // undefined
Color.Blue.getName() // “Blue"

Advantages:

  1. Using the automatic value you do not need to explicitly give values, but you can.
  2. Provides an Enum subclass with methods for converting literals to enums and visa-versa safely.
  3. Flow (and smart IDEs) can now track types and values correctly. Especially important for parameter and return types.
  4. IDEs will support code completion.

Disadvantages:

  1. Does require an extra library (really not a big deal these days).
  2. It needs a transpiler for Flow - not an issue if your already using Flow in the first place.
  3. You have to be careful when passing values (which are objects) back and forth with other vanilla JavsScript.



Here is the class:

class Enum {
value:number|string;

constructor(value:number|string = undefined) {
if (value === undefined) {
if (typeof this.constructor.prototype.iota === 'undefined') {
this.constructor.prototype.iota = 0;
}
value = this.constructor.prototype.iota++;
}

this.value = value;
}

getName():string {
let found = undefined;
Object.keys(this.constructor).forEach((k:string) => {
if (this.constructor[k].value === this.value) {
found = k;
}
});
return found;
}

toString():string {
return `${this.value}`;
}

static valueOf(value:number|string):undefined|this {
let found = undefined;
Object.keys(this).forEach((k:string) => {
if (this[k].value === value) {
found = this[k];
}
});
return found;
}
}

It is not a package because I was still playing around with it. I’d like to get some input from some JavaScript peeps about what can be improved or potential pit falls.