Proposal for Promise.prototype.flatten

# Aaron Silvas (5 years ago)

asilvas/proposal-promise-flatten

Looking for interest, and TC39 champion.

The basic idea is to provide a simpler, more consistent interface, and easier to read code over using try/catches for async code paths. Regardless of which errors are handled or ignored, it's treated as nothing more than another input in the result, not all that dissimilar to callbacks.

async function test(promise1, promise2, promise3) {
  const [, val1] = await promise1.flatten(); // ignore exceptions
  const [err, [val2, val3] = []] = await Promise.all([promise2, promise3]).flatten();

  if (err) throw err; // throw to caller

  return val1 + val2 + val3;
}

Original topic that spurred interest in this pattern: twitter.com/DavidWells/status/1119729914876284928

Spec discussions: twitter.com/Aaron_Silvas/status/1120721934730137601

Thanks,

Aaron

# guest271314 (5 years ago)

The term "flatten" or "flatmap" has also been used to refer to "flattening" a nested array; and/or "flattening" (output) of a potentially (arbitrarily) nested data structure (synchronous and asynchronous input/output).

"easier to read" is subjective; depends on the expectations of the reader. try..catch blocks could be considered explicitly "easier to read" by the names used: "try"; "catch".

The requirement appears to be a "reflect" pattern? E.g., see this answer stackoverflow.com/a/31424853 at Wait until all ES6 promises complete, even rejected promises

const reflect = p => p.then(v => ({v, status: "fulfilled" }),
                            e => ({e, status: "rejected" }));

reflect(promise).then((v => {
    console.log(v.status);});

See also tc39/proposal-promise-allSettled.

Is the expected result to write less code and handle exceptions implicitly?

# Jordan Harband (5 years ago)

Would this not work?

async function test(promise1, promise2, promise3) {
  const val1 = await promise1.catch(); // ignore exceptions
  const [val2, val3] = [] = await Promise.all([promise2, promise3]).catch();

  await Promise.all([promise1, promise2, promise3]); // throw to caller

  return val1 + val2 + val3;
}
# Bergi (5 years ago)

Hello,

I oppose this proposal.

In my opinion, mixing normal exceptions (promise rejections) with result-error-tuples is rather inconsistent than a more consistent interface. The only thing it might interface better with are node.js-style callbacks, but we already deprecated those in favour of promises. if (err) throw err; is reminiscent of this old style, it's no longer necessary to explicitly re-throw with modern syntax.

It's not even simpler, or better to read. The example you gave could easier be written as

async function test(promise1, promise2, promise3) {
   const val1 = await promise1.catch(err => void err); // ignore exceptions
   const [val2, val3] = await Promise.all([promise2, promise3]); // throw to caller

   return val1 + val2 + val3;
}

(Notice also that no default value for the destructuring is necessary, which is a rather error-prone part of your snippet). Using catch forces the programmer into explicitly providing a default value (even if undefined), which could go unnoticed otherwise. Can you please add an example where using .flatten() actually introduces an advantage?

If one needs a more powerful error handling approach, you should use then with two callbacks. (Getting a try-catch-else syntax stackoverflow.com/questions/4872170/javascript-try-catch-else-finally-like-python-java-ruby-etc for that would be nice, though).

As already mentioned, the name flatten is ill-suited for this method.

If you absolutely need this functionality, use .then(r=>[,r],e=>[e])

in your code, or put it inside a helper function. (Or use one from a library like scopsy/await-to-js).

We do not need this as a method in the EcmaScript standard.