tc39 / proposal-do-expressions

Proposal for `do` expressions
MIT License
1.11k stars 14 forks source link

Focus on `const` bindings #45

Open mcgwiz opened 5 years ago

mcgwiz commented 5 years ago

Seems the do concept has invited a wide range of interpretations. But, with modern JavaScript, the actual practical need (evidenced in part by the majority of code samples in other Issues in this repository) is to simplify code/patterns related to constant initializations. When applying Best Practices™, it is very common to have some logic that

  1. is idiomatically expressed with flow control or exception handling statements,
  2. not needed elsewhere and therefore suitable to be placed inline without a named function,
  3. can rely on closures for parameterization, and
  4. produces a value that is best treated as a constant. (An inline IIFE would reduce readability and should be refactored to a constant.)

The way to address this currently is to write, e.g.

// Sample 1
const calculation = (() => {
  try {
    switch(determinant) { 
      case 0: { return input * cofactor; }
      case 1: { return input * input; }
      default: { return 0; }
    }
  } 
  catch (e) {
    log(e);
    return -1;
  }
})();

Most of this follows from the common desire to use const bindings to improve code clarity and safety. This should be promoted by removing the syntactic noise of the IIFE.

Informal Proposal

By scoping this proposal to the constraints identified above, we can focus on a variation of const declaration. I propose one of the following two options: <= (left arrow) operator, or from keyword.

<= operator (location of operator alludes to assignment, angle-bracket alludes to a function body):

// Sample 2
const calculation <= {
  try {
    switch(determinant) { 
      case 0: { return input * cofactor; }
      case 1: { return input * input; }
      default: { return 0; }
    }
  } 
  catch (e) {
    log(e);
    return -1;
  }
};

from keyword (alludes to module system):

// Sample 3
const calculation from {
  try {
    switch(determinant) { 
      case 0: { return input * cofactor; }
      case 1: { return input * input; }
      default: { return 0; }
    }
  }
  catch (e) {
    log(e);
    return -1;
  }
};

These are essentially syntactic sugar for the original sample. There would be no form to sugarcoat expression-bodied IIFEs.

async/await:

The syntactic noise is exacerbated with async functions. Currently, e.g.

// Sample 4
const result = await (async () => { 
  try { return await db.getAll(); }
  catch { return await db.getActive(); }
})();

<= operator:

// Sample 5
const result <= await { 
  try { return await db.getAll(); }
  catch { return await db.getActive(); }
};

from keyword:

// Sample 5
const result from await { 
  try { return await db.getAll(); }
  catch { return await db.getActive(); }
};

In either case, the presence of await would imply the the use of the async before the opening {. await will cause the result of the Promise to be bound to the identifier. It is acceptable, but optional, to include async explicitly between await and {.

If await appears in the const initialization body ("initialization block"?) but the desire is to bind the Promise itself to the identifier, then the await keyword must be not be present and the async keyword becomes non-optional. This makes clear the const will refer to a Promise.

const initialization bodies would also support destructuring, just as if an IIFE were being used.

By focusing on a narrow best practice that, due to its frequency, is currently painful, const initialization bodies can be extremely beneficial to the community.