At cinch, I work on the inventory team and we manage all of the vehicle inventory, tracking the status, location and all kinds of other data for thousands of cars. We have an event driven architecture which means parts of our code respond to certain events which we subscribe to. We have a state machine/model which contains a LOT of logic to determine moving cars from one status to another. I came up with the following pattern when I noticed some hefty if
conditions in the codebase.
In this example I'm going to use robots as the subject as I write code to do with vehicles every day! 🚗
Let's imagine we want a function which disposes of a robot but we can only dispose of the robot if it's status is faulty
or damaged
. Below we setup our enum RobotStatus
to list our possible robot status and a Robot
interface to build the shape of our robot.
enum RobotStatus {
ready,
damaged,
faulty
}
interface Robot {
name: string
status: RobotStatus
}
function disposeRobot(robot: Robot): void {
if (robot.status === RobotStatus.damaged ||
robot.status === RobotStatus.faulty
) {
console.log('Disposing of robot...')
}
console.log('Robot in incorrect state to dispose...')
}
This is fine for now. But imagine if we had to start adding more checks for other status. Let's add some more for discontinued
, dead
, old
, rusty
, and dangerous
.
enum RobotStatus {
ready,
damaged,
faulty,
discontinued,
dead,
old,
rusty,
dangerous
}
interface Robot {
name: string
status: RobotStatus
}
function disposeRobot(robot: Robot): void {
if (robot.status === RobotStatus.damaged ||
robot.status === RobotStatus.faulty ||
robot.status === RobotStatus.discontinued ||
robot.status === RobotStatus.dead ||
robot.status === RobotStatus.old ||
robot.status === RobotStatus.rusty ||
robot.status === RobotStatus.dangerous ||
) {
console.log('Disposing of robot...')
}
console.log('Robot in incorrect state to dispose...')
}
Now that if
block is getting chunky and it stinks 👃.
Let's create an enum
containing our allowed disposable statuses.
enum DisposableStatus {
damaged,
faulty,
discontinued,
dead,
old,
rusty,
dangerous
}
JavaScript has an in
operator which will return true if the specified property exists in the specified object.
prop in object
This can clean up our if
block from above. Let's use it...
function disposeRobot(robot: Robot): void {
if (robot.status in DisposableStatus) {
console.log('Disposing of robot...')
}
console.log('Robot in incorrect state to dispose...')
}
TypeScript will compile the enum into a regular JavaScript object and these properties will exist at runtime 👍.
This is much more readable and easier to digest for your future self and other developers.
Top comments (7)
Does this mean
RobotStatus.damaged === DisposableStatus.damaged
? It is a cool mechanism, but also confusing to me. I prefer using string unions over enums, and one of the reasons is this comparison issue. To me, two enums should never be equal to each other like anint
and astring
cannot be equal (as in ===). JS is a mess because it allows too many non-trivial comparisons, and TS solves many of its problems. It feels weird to see this type of magic working. I would do something like the following instead:Sounds like a finite state machine to me! Something that web dev has yet to really embrace fully. This is a great example of how powerful FSMs are and why we should be using them a lot more often with our development. Great post! 👍
If you or anyone is interested in learning more about FSMs, React Podcast had a great episode on the very topic which you can find here: reactpodcast.com/episodes/5
Agreed, I'm an avid xState fan
Don't do this. It produces wrong results since it relies on the enum's internal value which is just a number - it matches wrong enum values to each other:
enum Type {
Disabled, // This is 1
Enabled, // This is 2
Pending, // This is 3
}
enum AllowedTypes {
Enabled, // This is 1
Pending, // This is 2
}
const state = Type.Enabled;
console.log(Type.Pending in AllowedTypes); // This checks if 3 is in { 1: 'Enabled', 2: 'Pending' } which is why it returns false
PS: You "could" technically make this work with string enums but you would have to make sure the value you assign matches the key and there are no duplicates (this is not checked by TS)
Ah yes, good spot! I had a check and the status is received as a string value from our database so in my case it works well.
I think Enum results in an object so there isn't any magic going on here, just normal JavaScript. A quick note on in, it checks prototype properties as well which in the world of useless micro optimization this would be a little slower.
Thanks Andrew.