tc39 / proposal-await-dictionary

A proposal to add Promise.ownProperties(), Promise.fromEntries() to ECMAScript
MIT License
86 stars 4 forks source link

object analogues for all vs allSettled vs race vs any #4

Open ljharb opened 3 years ago

ljharb commented 3 years ago

For a list of Promises, there's four combinators available.

I personally have use cases for both all and allSettled semantics; would we want all four for completeness?

ajvincent commented 3 years ago

I am hesitant on this. I think if you illustrate use cases you might convince me.

Regarding an allSettled equivalent for dictionaries, I really don't see it. Because the moment one of those promises rejects, the object is not constructible. I think. That said, we definitely could have a case for shaping the exception (i.e. naming the key or the value of the key that failed in the exception to raise).

For race and/or any, I also don't see it. Those to me indicate "either this property is defined, or that property is defined", and it doesn't make sense to me why we'd want it. (But then again, I missed the value of the Map case, so...)

I hesitate also because the more combinations we imagine, the more we bloat the specification. There was the debate over deep versus shallow already, and we have other debates coming (symbols? non-enumerable?).

At some point we might have to think about static functions wrapping around other promise functions to alter their behavior, particularly if we introduce combinatorics to handle all sorts of situations, such as deep promise walking. But I can't think of any ECMAScript functions to do such a modification of how other ECMAScript functions work, and that's a strange minefield to go into. (Edit: The best model I can imagine in this scenario is functions like Array.prototype.map, which take a visitor argument.)

I get that this proposal just appeared, and now is the time to brainstorm. Don't take this as shutting down the conversation. Take this as a note of "I don't know..." and show me the reasoning.

ljharb commented 3 years ago

Because the moment one of those promises rejects, the object is not constructible. I think.

Sure it is - the values of the constructed object would be the same settlement objects that Promise.allSettled returns.

There was the debate over deep versus shallow already, and we have other debates coming (symbols? non-enumerable?).

I don't see any use case whatsoever for dealing with anything other than enumerable own properties (string and symbol).

ajvincent commented 3 years ago

I'm going to put some specific cases regarding rejected promises in #5 as references.

ajvincent commented 3 years ago

OK, it's clear to me now that I misunderstood about allSettled. I did not realize that it resolves unconditionally when all the promises passed in are no longer pending, and that what it resolves to is an array of simple objects detailing the results. This changes things.

Given that, I can buy an analog for that, and even rewrite the polyfills to use it. But what would you call the dictionary equivalent of allSettled?

ljharb commented 3 years ago

I'm not really sure. If we're only providing a single method, then we could perhaps just take "settled" as implied.

mhofman commented 3 years ago

My suggestion for a less concise but still useful helper was Promise.allEntries, which simply unwrapped and resolved an entries like collection. The all prefix implies the same behavior as Promise.all, since I'm not sure the other ones make sense. It's not particularly difficult to write in userland, so it may not pass the bar for a useful addition to the language.

Promise.allEntries = (entries) =>
  Promise.all(Array.from(entries).map((entry) => Promise.all(entry)));

const obj = {
  foo: Promise.resolve("bar"),
};

const resolvedObject = Object.fromEntries(
  await Promise.allEntries(Object.entries(obj))
);

const map = new Map();
map.set(Promise.resolve("foo"), Promise.resolve("bar"));

const resolvedMap = new Map(await Promise.allEntries(map.entries()));
ljharb commented 3 years ago

Why wouldn’t the other ones make sense? All four have use cases for anything async, whether an iterable of promises or an object of promises.

mhofman commented 3 years ago

Because they wouldn't be usable directly with parts of the language that expect an entries collection?

What should allSettledEntries do, especially in the case of a promise in place of the key (entry[0])? The settled result pair isn't usable with Object.entries, and not very useful with the Map constructor. What should anyEntries (anyEntry?) do? Return a collection with a single entry where both the key and value first resolved?

ljharb commented 3 years ago

Sure it’s usable with Object.entries - the value would be a settlement object, destructurable like ([key, { value, reason, type }]) =>

mhofman commented 3 years ago

So only the value part of the entry tuple would be a settlement, the key part would still behave as all? Aka it'd be more like allEntriesWithValuesSettlement

const settledObject = Object.fromEntries(
  await Promise.allSettledEntries([[Promise.resolve("foo"), Promise.resolve("bar")]])
); // {foo: {status: "fulfilled", value: 'bar'}}
ljharb commented 3 years ago

Yes, keys can’t be async for a number of reasons, only values would be.

mhofman commented 3 years ago

In my suggestion for Promise.allEntries, keys can definitely be async as shown above.

ljharb commented 3 years ago

That seems like it would be very problematic since two keys could resolve to the same value, so ordering would be important, but you can’t necessarily know that in advance.

mhofman commented 3 years ago

I'm not sure I understand how it's different than entries collections today, except that the content of the keys might not be synchronously available. Anyway, probably not worth debating too long since it's the kind of helper that are probably seldom used and can easily be written by users.

ajvincent commented 1 year ago

Since this proposal appears to be revived, I'd like to raise a suggestion. Perhaps create a namespace under Promise: Promise.object or Promise.dictionary or some other name, where we could define a set of these methods.