A New Coding Style for Switch Statements in JavaScript/TypeScript
Bryan Hughes
Posted on May 16, 2019
I've recently adopted a new coding style for switch
statements in JavaScript (well, technically TypeScript), and I'm really curious to hear what you all think about it!
Before I discuss the style, let me explain the problem I'm trying to solve. It's not uncommon for switch statements to have somewhat-repeated code between cases, but with variations in each case such that you can't easily abstract it. Let's take this example, written in ES5 (I promise there's a reason why).
switch (body.type) {
case 'isBasic':
var entry = getBasicEntry(body.id);
console.log('Processing basic entry "' + entry.name + '"');
doBasicProcessing(entry);
break;
case 'isCustom':
var entry = getCustomEntry(body.id);
console.log('Processing custom entry "' + entry.name + '"');
doCustomprocessing(entry);
break;
default:
throw new Error('Unknown flag ' + myFlag);
}
Cool, it's a basic switch statement and it works fine. Now, let's convert this to ES2015+.
switch (body.type) {
case 'isBasic':
const entry = getBasicEntry(body.id);
console.log(`Processing basic entry "${entry.name}"`);
doBasicProcessing(entry);
break;
case 'isCustom':
const entry = getCustomEntry(body.id);
console.log(`Processing custom entry "${entry.name}"`);
doCustomprocessing(entry);
break;
default:
throw new Error(`Unknown flag ${myFlag}`);
}
Uh-oh, we now get a SyntaxError
exception when we run this code! The problem here is that const
variable name entry
cannot be declared twice in the same scope. Because of how scoping rules work, the entire switch
statement is one single scope.
We could solve this by naming each entry
with a different variable name, but honestly I find that kind of annoying. We could also create a helper function for each case, but that feels excessive to me for such short case statements.
So here's what I figured out, and I've started using it in all my code:
switch (body.type) {
case 'isBasic': {
const entry = getBasicEntry(body.id);
console.log(`Processing basic entry "${entry.name}"`);
doBasicProcessing(entry);
break;
}
case 'isCustom': {
const entry = getCustomEntry(body.id);
console.log(`Processing custom entry "${entry.name}"`);
doCustomprocessing(entry);
break;
}
default: {
throw new Error(`Unknown flag ${myFlag}`);
}
}
I wrap each case statement in {}
, which creates a new scope on a per-case basis. This solves the problem, and I think is kind of elegant myself. It's not very common though.
What do you all think? Does this seem like a good approach? Do you have a different approach that you use? I'd love to hear it in the comments!
Update:
Thank you all for the discussion so far, it's been great! I ended up creating a new module called conditional-reduce that automates another approach I hadn't thought of before:
const { reduce } = require('conditional-reduce');
console.log(reduce('dog', {
dog: () => 'Dogs are great pets',
cat: () => 'Cat\'s are also great'
})); // Prints "Dogs are great pets"
console.log(reduce('bird', {
dog: () => 'Dogs are great pets',
cat: () => 'Cat\'s are also great'
})); // Throws 'Invalid conditional value "bird"' exception
const { curry } = require('conditional-reduce');
const dogReducer = curry({
dog: () => 'Dogs are great pets',
cat: () => 'Cat\'s are also great'
});
console.log(dogReducer('dog')); // Prints "Dogs are great pets"
console.log(dogReducer('bird')); // Throws 'Invalid conditional value "bird"' exception
Thank you @avalander and @john_papa for the discussion that prompted this!
Posted on May 16, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.