Sorry that I am reviving to this old topic, but it matches best what I desparately need to be added to JS. Because it will affect thousands if not more really awful constructs in my code which uses Promise.all
and similar.
I always break my fingers by writing something like
const [ a,b,c,d,e ] = await Promise.all([ u,v,w,x,y ]);
Yes, this hurts me. Badly. As I need that nearly in 20% of my routines. Because concurrency is important!
What I'd rather like to see is
const await [ a,b,c,d,e ] = [ u,v,w,x,y ];
This looks very like for await
. Adding it is possible as it currently is already detected as syntax error. So it creates no ambiguity.
Promise.all
vs. Promise.allSettled
Note that I definitively do not agree to posts like "avoid Promise.all at all cost" due to people killing their own server by sending billions of fetch()
in parallel, and hence recommend even more complex things like p-throttle. In contrast I always limit the number of concurrent fetch requests and live happily with Promise.all
afterwards. (So I do something like const fetch20 = throttle(20, fetch);
and then use fetch20
instead of fetch
. With some suitable implementation of throttle
. This has the added opportunity to have some fetch5
or fetch1
arround, too, which are independently queued parallel to the other fetches).
The question about above "const await", "let await" and (perhaps) "var await" construct is: Shall it use Promise.all
or Promise.allSettled
? I vote for the first, because it should NOT wait until all Promises settle but throw early. If this is a concern, use .catch()
on the promises which shall not throw.
Also something like Promise.allSettled
can be implemented with some syntax like Promise.all(promises.map(_ => _.catch(e => ({e})))
, or, in my wanted case
const await [...a] = promises.map(_ => _.then(r => ({r}), e => ({e})));
You probably get the idea.
I think such code is much more readable than
const a = Promise.allSettled(promises);
Why? Because there I do not really see what is going on without a deep understanding of what Promise.allSettled()
does. In contrast in the above way I know, that I get some Object {r}
which contains the result and some object {e}
which contains the error. No need to know how to do introspection on some obscure thing called Promise. Just some clear
if (a[0].e) // Oh there was some error on the first object
other discussed form of await in destructuring
I would vote against using await
within the destructuring as shown in the other posts. Either all or nothing, because everything in between makes things unnecessary complex. And complexity shall be avoided at all cost.
Hence PLEASE DO NOT EVEN THINK ABOUT something like:
const [ await a, b ] = api();
or even more horrible
const { await a, b } = api();
Either
const [ _a, b ] = api();
const a = await _a;
or
const [ _a, b ] = api();
const await a = _a;
Why?
Because it is not really clear what the author wanted to say in something like
const [ await a, b, await c, d, await e ] = api();
Compare
(A)
const [ _a, b, _c, d, _e ] = api();
const await [ a, c, e ] = [ _a, _c, _e ];
vs. (which already works today):
(B)
const [ _a, b, _c, d, _e ] = api();
const a = await _a;
const c = await _c;
const e = await _e;
Which one was what the author preferred? You may argue "that makes no difference" but I definitively see some different behavior when it comes to error propagation.
It is right, that one probably expects that the error flow should perform as in "(A)". But then look at the construct itself again. await a, b, await c, d, await e
is just uglyashell if you ask me .. and I alread hate to write things like this.
In contrast for await
is already there. Why not accompany it with const await
and let await
?
And, btw, perhaps var await
is bad, too, because of scoping rules. So if you ask me, where to add await
, I'd vote for let
and const
but not var
. var
has it's use, but only if you really need to make forward declarations but want to keep the declaration at the definition (for DRY code). So in my code I do not need var
often and do not see any added value in having var await
, except, perhaps, for some symmetry.
So if adding let await
and const await
gives var await
for free in the engines, Id say keep it. Else leave
var` alone.
Perhaps even better optimization
const await { a, b. c } = something()
should be syntactic sugar for
const _a = something();
const [a,b,c] = await Promise.all([a,b,c]);
and not
const _a = await something();
const [a,b,c] = await Promise.all([a,b,c]);
of course. I only write this here because
const a = await api();
and
const await a = api();
happen to give the same result. But it might be that they are evaluated on some
different implementation path in the engines.
AFAICS const await
can be far better optimized, because the destructuring is already known in advance (at least most times), while with Promise.all
the destructuring can only happen after the Promises settle for real.
And finally
There shall be some difference between Promise.all()
and destructuring:
With destructuring only the affected Promises (or Futures) are awaited.
While with Promise.all
really all Promises are awaited.
Example:
const promises_array = [u,v,w,x,y,z];
// only u,v,w are awaited in parallel, x,y,z are left alone
const await [a,b,c] = promises_array;
// const await [a,b,c] = await Promise.all(promises_array);
// would await all promises in parallel unneccesarily
// so you must .slice() the array:
const await [a,b,c] = await Promise.all(promises_array.slice(0,3));
// which is highly error prone, consider adding one variable
//const await [a,b,c,d] = await Promise.all(promises_array.slice(0,3));
// OOPS forgot to increment the slice, too.
// And following (consider "await api()" is something more complex):
const await { a,b,c } = await api();
// needs to be expressed, today, with something like:
const [a,b,c] = await Promise.await((_=>[_.a,_.b._.c])(await api()));
At least that is what I see in my code. Far too often. Which makes it harder to understand than necessary, at least compared to the way with const await
.