Proposal: “Statements as Expressions” using do

A proposal for do statements has been classified as Stage 0 for a while, and it might be an interesting solution for some problems we can find in JavaScript.

Improve this article
Nicolás Bevacqua
| 5 minute read | 6

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

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 (
    <Home />
    { loggedIn && <LogoutButton /> || <LoginButton /> }

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 (
    <Home />
      do {
        if (loggedIn) {
          <LogoutButton />
        } else {
          <LoginButton />

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.

Liked the article? Subscribe below to get an email when new articles come out! Also, follow @ponyfoo on Twitter and @ponyfoo on Facebook.
One-click unsubscribe, anytime. Learn more.

Comments (6)

Jonah Williams wrote

do seems 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.

Šime Vidas wrote

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 dates array

dates // return the initialized array };

Skyler Nelson wrote

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’.

James Edward Lewis II wrote

A potential problem for those who like omitting semicolons could happen when using a do expression right before a while loop followed by an empty statement:

do {
}; //  `do` expression-statement, followed by an empty `while` loop
while (console.log('random'), Math.random() < 0.8); // logs one 'lol' and at least one 'random'

Now try omitting the semicolon; Automatic Semicolon Insertion won’t work here, because this looks like a do-while loop:

do {
} //  `do`-`while` loop
while (console.log('random'), Math.random() < 0.8); // logs at least one pair of 'lol' and 'random' followed by a final 'random'

Then again, it seems that do-while loops are rare to begin with, and do expressions 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 a do-while loop, so this isn’t a severe problem.

James Edward Lewis II wrote

(I just noticed an error, that second example just logs pairs of 'lol' and 'random' without a final 'random')

James Edward Lewis II wrote

I also just found the actual strawman proposal for do expressions, which explicitly disallows them from being expression-statements, probably for the reason I mentioned in my main comment.