When JavaScript expressions are evaluated, they produce a single value.
3 * 10
// <- 30
If we want to add a condition in an expression, we need to use ternary expressions or logical operators. The following example displays both alternatives, although the former is usually preferred.
[1, -1, -0.5].map(x => x > 0 ? x * 10 : -x * 10)
// <- [10, 10, 5]
[1, -1, -0.5].map(x => x > 0 && x * 10 || -x * 10)
// <- [10, 10, 5]
It can be confusing to create side effects, but you can achieve that through use of commas.
sideEffect(), 3 * 10
// <- 30
It’s not possible to declare variables you might need for temporary storage within an expression. As such, you typically extract these variables into the enclosing scope. More often than not, that’s better for readability anyways, so it doesn’t have a hugely negative impact.
Using do
The do expression proposal lets you write a block of statements to be evaluated. The “completion value” is returned as the result of a do expression. In the following example, there’s just a 3 * 10 expression in our block, so that’s our completion value, and 30 is returned.
do { 3 * 10 } // just an expression
// <- 30
The following bit of code is equivalent to the two .map examples we saw earlier. In this case, we use a do expression, allowing us to use if and else instead of ternary or logical operators.
[1, -1, -0.5].map(x => do { if (x > 0) { x * 10 } else { -x * 10 } })
// <- [10, 10, 5]
Side effects in do expressions become easier to read, and we are able to declare variables. In the following example we’re purposely missing a return statement, as do expressions already implicitly return, as we saw in the case of the if / else example. Naturally, const and let variables declared inside a do block are scoped to that block, while var variables are scoped to the containing function.
var data = do {
const data = pullSomeData()
doSomethingElse() // sideEffect
data
}
Using do Today
It’s easy, there’s a Babel plugin we can use.
npm install --save-dev babel-plugin-syntax-do-expressions
Then add the following to your .babelrc file or the babel property in package.json.
{
"plugins": ["syntax-do-expressions"]
}
That’s it.
Whenever you run the Babel CLI, it’ll understand do expressions.
Conditionals in JSX
A while back I wrote about “the weird parts” of using JSX – the JavaScript syntax extension Facebook built to help you write templates for React apps. Back then, I mentioned how sometimes you have to write code like the following when you want to conditionally render a piece of markup.
return (
<nav>
<Home />
{ loggedIn && <LogoutButton /> || <LoginButton /> }
</nav>
);
With do expressions, you could get rid of the weird-looking and oft-confusing logical operators. This makes the code easier to read and saves you from having to deal with falsy expressions like 0 or ''.
return (
<nav>
<Home />
{
do {
if (loggedIn) {
<LogoutButton />
} else {
<LoginButton />
}
}
}
</nav>
)
When there’s larger pieces of markup it becomes more elegant to be able to use statements instead of expressions – and do let’s you do that. The do syntax makes it easier to read conditionals, allows you to use variables, and makes it clear when part of an expression is a side effect.
Comments (6)
doseems like a good solution to a problem I’ve been running into with React components and JSX where I either create tons of helper function to initialize sub-components or nest ternaries for assignment. I’ll plan on trying it out in some projects this week.One popular (I think) use case would be to initialize a variable “on the spot”, without having to use an IIFE or move the code into a separate function defined somewhere else, e.g.
let dates = do { let dates = []; let start, end, exclude; // helper variables
// the algorithm that initializes the
datesarraydates // return the initialized array };
This is really great in terms of what it enables, but is there any reason to overload the ‘do’ keyword? We already have ‘do…while’ loops, which use essentially the exact same syntax but with a ‘while’ bit at the end, and aren’t usable as expressions.
It would be funny to unify them and make do…while loops (and maybe loops of all kinds) usable as expressions, where a missing ‘while’ at the end of a ‘do’ expression didn’t trigger a loop, e.g.
let foo = do { bar(list.pop()); } while (condition);
Would pop off the elements of list, run them through bar and assign the result to foo until a condition was met. I’m somewhat hard pressed to think of any real use cases for this though. With some tuning, it could be turned into a list/generator/whatever comprehension syntax, e.g. something like
let foo = [ …do { list.pop(); } while (condition) ];
Would populate an array with of all the elements popped from list until condition was met. But perhaps I’m going too far afield, given the probably incidental reuse of the keyword ‘do’.
A potential problem for those who like omitting semicolons could happen when using a
doexpression right before awhileloop followed by an empty statement:Now try omitting the semicolon; Automatic Semicolon Insertion won’t work here, because this looks like a
do-whileloop:Then again, it seems that
do-whileloops are rare to begin with, anddoexpressions wouldn’t commonly be used as expression-statements, but rather as the right-hands of assignments, or as arguments to a function call, and in both cases they can’t be interpreted as starting ado-whileloop, so this isn’t a severe problem.(I just noticed an error, that second example just logs pairs of
'lol'and'random'without a final'random')I also just found the actual strawman proposal for
doexpressions, which explicitly disallows them from being expression-statements, probably for the reason I mentioned in my main comment.