Skip to content

tc39/proposal-await-dictionary

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

34 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Await dictionary of Promises

Status

Stage: 1

Champions:

Authors:

Motivation

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;

Proposed Solution

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]> }>

Existing solutions

Library Own Symbols
Bluebird.props
combine-promises
p-props

Implementations

Polyfill/transpiler implementations

None.

Native implementations

None.

Q&A

Why not a deep-copy option?

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.

Why only own keys?

This follows other builtins such as Object.keys and also matches https://github.com/tc39/proposal-joint-iteration.

What about symbol keys?

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.

Alternatives considered

Promise.ownProperties

const {
  shape,
  color,
  mass,
} = await Promise.ownProperties({
  shape: getShape(),
  color: getColor(),
  mass: getMass(),
});

Promise.fromEntries

const {
  shape,
  color,
  mass,
} = await Promise.fromEntries(Object.entries({
  shape: getShape(),
  color: getColor(),
  mass: getMass(),
}));

Promise.all overload

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.

Dedicated syntax

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);

About

A proposal to add Promise.allKeyed to ECMAScript

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors 4

  •  
  •  
  •  
  •