I recently added a feature to some client code. I won't show the code here, but it was a simple copy-paste with a minor set of changes to ensure the new code executed. The code failed miserably and ran me down an interesting rabbit hole.
The original version of the code when I began working on this project was a long IF-THEN, ELSE-IF block. It made sense to shift to a SWITCH statement ... making the code easier to read and understand.
I ran into an issue that is one of those, "I knew that" moments where my understanding of JavaScript's internal workings went up another notch.
Original Code
The original IF-ELSE, ELSE-IF block went something like this ...
const demoSwitch = () => {
const demo = {
first: 1
};
if (demo.first === 1) {
const result = 1;
return result;
} else if (demo.second === 2) {
const item = 2;
return item;
} else {
return -1;
}
};
Now, this example is super-simplified, to allow this article to focus on the true issue. There were specific reasons in the original code why result
and item
were used. This allowed the first round of refactoring to work correctly, as we'll see later.
Conversion to Switch
With a basic understanding of the code above, it got converted to something like this ...
const demoSwitch = () => {
const demo = {
first: 1
};
switch (demo.first) {
case 1:
const result = 1;
return result;
case 2:
const item = 2;
return item;
default:
return -1;
}
};
Looking at this code, an experienced developer would begin to question some of the decisions that lead to the return lines within the CASES. However, this is simplified code and in its original form, the code has good reason the maintain this form.
Breaking the Switch
Now, as was said earlier, a new feature was added that paralleled another existing feature. The switch above needed an additional case.
This is where the problem started (and ended).
const demoSwitch = () => {
const demo = {
first: 1
};
switch (demo.first) {
case 1:
const result = 1;
return result;
case 2:
const item = 2;
return item;
case 3:
const result = 3;
return result;
default:
return -1;
}
};
This code (before it is even executed) returns the following error: Uncaught SyntaxError: Identifier 'result' has already been declared
.
I was stumped for a minute, tried a minor adjustment to the code.
const demoSwitch = () => {
const demo = {
first: 1
};
let result = -1;
switch (demo.first) {
case 1:
result = 1;
return result;
case 2:
const item = 2;
return item;
case 3:
result = 3;
return result;
default:
return result;
}
};
This worked.
Here's another pattern suggested in the comments ...
const demoSwitch = () => {
const demo = {
first: 1
};
switch (demo.first) {
case 1: {
const result = 1;
return result;
}
case 2: {
const item = 2;
return item;
}
case 3: {
const result = 3;
return result;
}
default:
return -1;
}
};
This pattern works, as well!
Thanks to Kostia Palchyk.
Conclusion
Basically, this issue was about scope.
As a reminder:
- Declaring a variable using
var
uses the function-level scope. - Declarations using
let
andconst
are block-level scoped (think, wrapped in parentheses{}
).
If the variable result
had been declared using:
-
var
, it would have been hoisted and redeclaration would have occurred later in the code. -
let
, declaration and redeclaration at the block-level would have occurred.
The variable(s) were declared using const
, and therefore could not be redeclared at the same block-level.
While this seems like a simple thing, it is one of those little issues that can cause some consternation when a developer comes across it.