Modify Promise.all() to accept an Object as a parameter

# bread at mailed.me.uk (9 years ago)

This proposal came out of tc39/ecmascript-asyncawait#25

The proposal is to augment Promise.all() to accept an Object as a parameter, which return a corresponding object (or Promise for such an object) such the each field of the Object, if it were a Promise is resolved, and if not it is otherwise returned unmodified.

The exact semantics are such that each data member of the Object is treated as if it were an Array entry as passed to Promise.all() currently.

The purpose of the modification is to allow labelling of each Promise at a point close to the value of the Promise to quickly and easily show the developers intent.

Implementation is relatively easy, and Promise implementation agnostic (although the simplicity of the API makes implementation-specific optimization possible).

No view has been taken on how non-Promise/value members of the Object should be treated (ignored? throw Error?), or whether nested Objects should be resolved (potentially confusing, but useful for combining results from other calls that return Objects containing Promises for final resolution).

For example (taken from tc39/ecmascript-asyncawait#25):

var r = await Promise.all({ URL:getUrl("home"), input:doUserInput(elem) }) ;

redirect(r.URL+"?"+r.input); ...as opposed to: var r = await Promise.all([ getUrl("home"), doUserInput(elem) ]) ;

redirect(r[0]+"?"+r[1]); This has the advantage of keeping the operation and name in close proximity, making the intent easier to spot, is fully ES5 friendly with respect to the await expression and response, allows for nested parallel Promises, and in any case is a runtime implementation and so the programmer can alias the function.

# Marius Gundersen (9 years ago)

With destructuring you can do:

let [url, input] = await Promise.all([
  getUrl("home"),
  doUserInput(elem)
]);

redirect(url+"?"+input);

Marius Gundersen

# Adam Eisenreich (5 years ago)

Back when async/await was introduced I struggeled quite a bit with promises arrays that have conditional promises. RxJS is moving from array only support in theirs operators to objects too, there seems to be an actual trend going on. Is there any reason against?

# Jordan Harband (5 years ago)

The current API accepts an iterable, which means any object that has Symbol.iterator, such as an array or a Set.

Throwing when it receives a non-iterable object is an important tool to catch bugs. If Promise.all was made to accept a non-iterable object as well, I suspect many bugs would go uncaught.

# Bradford Smith (5 years ago)

Promise.all(Object.values(myObjWithPromiseValues)).then(...)

# Jacob Bloom (5 years ago)

What about special handling for Maps? Maybe something like

const requests = new Map();
requests.set('reqA', fetch('...'));
requests.set('reqB', fetch('...'));
const responses = await Promise.all(requests);
console.log(
  responses.get('reqA'),
  responses.get('reqB'),
);

...which would continue to fail for most objects and would encourage using Map over POJOs for map-like data structures. (Since Maps are iterable, this may break some existing code but I doubt it)

# Boris Zbarsky (5 years ago)

On 10/12/19 12:52 AM, Jacob Bloom wrote:

const responses = await Promise.all(requests);

As opposed to:

const responses = await Primise.all(requests.values());

which works right now?

# Cyril Auburtin (5 years ago)

OP probably means he would like Promise.all to return an object as well, if an object if given

It's possible to hack an object to be iterable, but Promise.all already return an array unfortunately

# Isiah Meadows (5 years ago)

Maybe Promise.join(object)? Also, if a map is passed (or any iterable), it should be joined into a map.

At the most basic level:

Promise.join = (o) => {
    let isMap = o[Symbol.iterator] != null
    let ps = (
        isMap ? Array.from : Object.entries
    )(o)
    let ks = ps.map(p => p[0])
    return Promise.all(ps.map(p => p[1]))
    .then(vs => {
        let ps = vs.map((v, i) => [ks[i], v])
        return isMap
            ? new Map(ps)
            : Object.fromEntries(ps)
    })
}
# Cyril Auburtin (5 years ago)

maybe a naming like: Promise.allObject

Promise.allObject = obj => {
  if (obj && !obj[Symbol.iterator]) {
    return Promise.all(
      Object.entries(obj).map(async ([k, v]) => [k, await v])
    )
      .then(Object.fromEntries);
  }
  return Promise.all(obj);
}

var delay = t => new Promise(r => setTimeout(r, t));

console.time(1); console.log(await Promise.allObject({foo:
delay(110).then(()=>1), bar: delay(120).then(()=>2)})); console.timeEnd(1)
# Michał Wadas (5 years ago)

Established name is Promise.properties

# Michael Luder-Rosefield (5 years ago)

The RSVP library uses Promise.hash, which seems sensible enough that I'm surprised no-one has mentioned or suggested it here.

Dammit babies, you've got to be kind.

# Tab Atkins Jr. (5 years ago)

On Fri, Oct 11, 2019 at 9:53 PM Jacob Bloom <mr.jacob.bloom at gmail.com> wrote:

What about special handling for Maps? Maybe something like

const requests = new Map();
requests.set('reqA', fetch('...'));
requests.set('reqB', fetch('...'));
const responses = await Promise.all(requests);
console.log(
  responses.get('reqA'),
  responses.get('reqB'),
);

...which would continue to fail for most objects and would encourage using Map over POJOs for map-like data structures. (Since Maps are iterable, this may break some existing code but I doubt it)

Hm. Since the functor instance over Maps is just the values, and Promise.all is just a special-cased traverse, I think this makes sense to me:

Map<keyType, Promise<valueType>> => Promise<Map<keyType, valueType>>

I'm fine with this living in another method name, tho.