Stage: 1
Champions:
- Ashley Claymore (@acutmore)
- Jordan Harband (@ljharb)
- Chris de Almeida (@ctcpip)
- TBC: Rick Waldron (@rwaldron)
Authors:
- Alexander J. Vincent (@ajvincent)
- Ashley Claymore (@acutmore)
await
on individual properties creates a waterfall, rather than running requests in parallel:
const obj = {
shape: await getShape(),
color: await getColor(),
mass: await getMass(),
};
Promise.all
helps, but is based on order, rather than names, which could lead to mixups:
const [
color,
shape,
mass,
] = await Promise.all([
getShape(),
getColor(),
getMass(),
]);
Solutions using existing syntax can be verbose and pollute the number of variables in scope:
const shapeRequest = getShape();
const colorRequest = getColor();
const massRequest = getMass();
const shape = await shapeRequest;
const color = await colorRequest;
const mass = await massRequest;
const {
shape,
color,
mass,
} = await Promise.allKeyed({
shape: getShape(),
color: getColor(),
mass: getMass(),
});
This intentionally follows the shape of https://github.com/tc39/proposal-joint-iteration.
As Iterator.zip
is to Promise.all
Promise.all = (Array<Promise<T>>) => Promise<Array<T>>
Iterator.zip = (Array<Iterator<T>>) => Iterator<Array<T>>
Promise.allKeyed
is to Iterator.zipKeyed
type Dict<V> = { [k: string | symbol]: V };
Promise.allKeyed = <D extends Dict<Promise<any>>>(promises: D)
=> Promise <{ [k in keyof D]: Awaited<D[k]> }>
Iterator.zipKeyed = <D extends Dict<Iterator<any>>>(iterables: D)
=> Iterator<{ [k in keyof D]: Nexted<D[k]> }>
Library | Own | Symbols |
---|---|---|
Bluebird.props | ✅ | ❌ |
combine-promises | ✅ | ❌ |
p-props | ✅ | ❌ |
None.
None.
JSON.stringify
aside, it is not common for builtin JavaScript APIs to traverse arbitrary objects deeply.
Array.prototype.flat
is deep, but only for the well defined boundaries of arrays.
This follows other builtins such as Object.keys
and also matches https://github.com/tc39/proposal-joint-iteration.
All enumerable properties are used, included enumerable symbols.
This matches https://github.com/tc39/proposal-joint-iteration.
This does differ from existing solutions, which follow Object.keys
semantics (ignoring symbols). This difference is not perceived to be an issue due to the low usage of own enumerable symbols.
const {
shape,
color,
mass,
} = await Promise.ownProperties({
shape: getShape(),
color: getColor(),
mass: getMass(),
});
const {
shape,
color,
mass,
} = await Promise.fromEntries(Object.entries({
shape: getShape(),
color: getColor(),
mass: getMass(),
}));
Dispatch depending if the argument is an iterable or not.
const {
shape,
color,
mass,
} = await Promise.all({
shape: getShape(),
color: getColor(),
mass: getMass(),
});
"This would avoid introducing a new name to the API surface. However, there is discomfort with the shape of the output depending on the shape of the input, and the risk of accidentally passing multiple arguments instead of an array. For example:
Promise.all(p1, p2, p3); // ❌ should have been `Promise.all([p1, p2, p3])`
While this currently throws as the p1
is not iterable, the overload would start to allow this call but not do what the caller intended.
Inspired from other languages such as Swift - see: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency#Calling-Asynchronous-Functions-in-Parallel
async const shape = getShape();
async const color = getColor();
async const mass = getMass();
const obj = await {
shape,
color,
mass: Math.max(0, mass),
};
All references to an async const
identifier within await <exp>
are implicitly awaited.
The above code would be (roughly) equivalent to:
const $0 = getShape();
const $1 = getColor();
const $2 = getMass();
const obj = await ((shape, color, mass) => ({
shape,
color,
mass: Math.max(0, mass),
}))(await $0, await $1, await $2);