Showcase stronger examples
Closed this issue ยท 6 comments
Hi! Thanks for drafting this proposal. I certainly feel the pain behind every example provided in the README. However, I must admit all of them can be solved and in a more elegant way than creating a write-once const
. Allow me to demonstrate.
Example 1
let value;
if (someCondition) {
value = 1;
} else if (someOtherCondition) {
value = 2;
} else {
value = 3;
}
This isn't really an assignment of value but computing of value. There's a logic to that computing (conditionals), and so it belongs to a function:
function computeValue() {
if (someCondition) return 1
if (someOtherCondition) return 2
return 3
}
const value = computeValue()
This is a great pattern that makes the computation explicit, testable in isolation (and you do need to test computeValue
!), and keeps the original assignment to value
simple while still using const
.
Example 2
let value1, value2;
if (someCondition) {
value1 = 1;
value2 = 5;
} else if (someOtherCondition) {
value1 = 2;
value2 = 10;
} else {
value1 = 3;
value2 = 15;
}
This should be solved by moving out the computation for value1
and value2
as well. However, since the emphasis of this example is in multiple values, and those values are assigned based on shared conditions, this strongly suggests that a choice of a better data structure should be considered.
function computeValue() {
if (someCondition) {
return { a: 1, b: 5 }
}
if (someOtherCondition) {
return { a: 2, b: 10 }
}
return { a: 3, b: 15 }
}
const value = computeValue()
// value.a / value.b
Collocation on the data structure level is generally beneficial but you can disregard this suggestion if the relevance of value1
and value2
is purely coincidental. If it is, then the same suggestion from Example 1 applies without fault to this one.
Example 3
async function doSomething() {
let result;
try {
result = await someOperationThatMightFail();
} catch (error) {
return;
}
doSomethingWith(result);
}
This is a common pitfall when handling async/await. And yet I don't think it has to be solved on the const
level.
Take a look at my approach to this problem in until
:
const [error, value] = await until(() => someOperationThatMightFail())
I'd argue this example is out of scope of your proposal as it involves not only the value assignment but also error handling, which you can neither disregard nor accommodate within the current proposal.
If anything, this hints at a different proposal to help wrap async/await operations in something like
until
. APromise.until()
, perhaps?
Concerns
In general, I see the proposal to the change of the const
behavior to be detrimental to the expectations and the benefits which the const
was designed to provide.
Please note that per original intention, const
is only ever meant to be used for module-wide constants. It never stood for an immutable version of var
, but it often gets misused that way. Overall, let
is preferred for variable assignments, and it already behaves as you are proposing.
TLDR const for top level module-scope constants only, and SCREAMING_CASE only. Otherwise let and call it a day.
โ Source
I know this is somewhat controversial but if the folks behind the spec say we are misusing const
then, perhaps, we are misusing const
. Suggesting additional, branching behaviors to it will not make things easier.
Thanks for the feedback. Indeed, I intentionally did not show refactors using functions because it's pretty clear that most developers are not doing that, otherwise I wouldn't be able to find examples of the patterns I mention. (Finding these patterns in the ESLint source code is what led me to write up this proposal.) I added a new FAQ to cover this because you're the second person to ask about it.
Please note that per original intention,
const
is only ever meant to be used for module-wide constants. It never stood for an immutable version ofvar
, but it often gets misused that way. Overall,let
is preferred for variable assignments, and it already behaves as you are proposing.
Through ESLint, I've been watching how people are actually using const
and let
for nine years now. Very shortly after ES2015 was released, people started using const
not just for module-wide constants, but for any immutable bindings. That's why prefer-const
is such a popular ESLint rule. At this point, I think it's fair to say that const
is a general-purpose immutable binding.
Your point about getting better examples is a good one. I think it's important for the explainer to show simple examples that illustrate the usage patterns and then link off to more complex, real-world examples (as I did with the try-catch
use case). I'm continuing to look for those real-world examples, but there's only so many hours in the day. ๐
I also profoundly disagree that const
was only "intended" for module-level constants - it was intended for any binding that can't be reassigned, which is how it's used.
@ljharb, I believe I'm quoting one of the folks who worked on the spec. I agree with you, in practice it's used completely differently. Keeping the specs in-line with reality is a commendable effort, and if this proposal makes it so, it would be good.
That's just one person, 2+ years after the feature was shipped in the spec. That does not reflect the intent of the committee, only the opinion of one person.
@nzakas just to offer another voice here, I saw your examples and thought they were great and immediately understandable and following much of code in the wild ๐
More examples are always good, but the existing ones are very good motivators in my opinion.
Thanks @karlhorky ๐