tc39/proposal-promise-allSettled

Bikeshedding about name

Closed this issue Β· 59 comments

Should we call this allSettled or something else? What other name ideas have been considered?

settleAll would make more sense to me. It would also follow the pattern of methods being verbs, arrays have reverse not reversed same with join, fill, race on Promise.

I've also seen settle used in libraries

What's a little strange to me about settle or settleAll is it makes me think that it will modify the Promises that are passed into it (which it won't). (I like join as a name for this, but I imagine that'd be confusing given that Bluebird uses the same name for something else.)

@littledan could you explain the reasoning behind .join ?
I immediately think of the array method and don’t really make the connection here, especially as this would return an array

OK, good point, join probably isn't the best idea.

@littledan in what way do you think "settleAll" would manipulate the Promise?

It sounds to me like it would make the promises provided as input become resolved.

I think the userland precedent here is pretty compelling for allSettled. I'd also be open to just "settled", under the assumption that being a static method makes it implicit that it will apply to all its arguments.

https://www.npmjs.com/package/rsvp calls the returned object (inside the array) a state object should we stick with this convention?

Each state object will either indicate fulfillment or rejection, and provide the corresponding value or reason. The states will take one of the following formats:

{ state: 'fulfilled', value: value }
  or
{ state: 'rejected', reason: reason }

Sounds fine but this seems related to a different issue :-)

Yup @ljharb you're right, i wasn't sure whether it needed a separate thread

Bluebird docs call it settleAll BTW.

Since we're bikeshedding, my 2 cents:

Setting aside userland APIs for a moment, I think it's far more common (and also accurate), both from the perspective of stuff written about JS, and also course materials, to refer to a promise that's either rejected or fulfilled as resolved.

There's already a Promise.resolve(..) for creating an already resolved (either fulfilled or rejected) promise. So, going on that precedent, I think we should stick with that terminology, and call the API Promise.allResolved(..).

Other options would be Promise.every(..) or Promise.any(..).

It sounds to me like it would make the promises provided as input become resolved.

I strongly agree with Dan here. Quoting from the very first sentence of the spec here:

"...is to want to settle all promises within an array." That to me reads as strongly suggestive of modifying the states of promises, rather than wait on them to be settled (aka, resolved).

Promise.resolve doesn't necessarily generate an already-resolved promise tho - Promise.resolve(new Promise(() => {})) will create a forever-pending promise. I think that the terms "resolved" and "fulfilled" are often confused in practice, and it'd be better to avoid that nomenclature.

Promise.resolve doesn't necessarily generate an already-resolved promise tho - Promise.resolve(new Promise(() => {}))

OK, fine, but Promise.allSettled([ new Promise( ()=>{} ) ]) won't ever get a settled promise, either, so I think that's just a moot academic point.

I think that the terms "resolved" and "fulfilled" are often confused in practice, and it'd be better to avoid that nomenclature.

I haven't see much evidence of these terms being confused. Any links to elaborate on that? In my books, I was very careful to spell that out, and I've read many other blogs which similarly stick to those words, and don't seem to be misleading.

I also haven't seen evidence (again, other than userland APIs) for the word "settled" being commonly used to disambiguate in that confusion... any links?

Bluebird uses "settled"; I haven't seen it commonly used otherwise.

I think that the subtleties of "resolved" are lost on many, who often seem to discuss promises as being "resolved or rejected" - when in fact they can be resolved TO rejected, fulfilled, or "pending".

Expressing my preference here for settled rather than allSettled. To be clear: not a rejection, I celebrate this feature anyway.

I believe this matches just fine with the idioms used for other static methods as .race and .all and an eventual .any.

Also, I don't see a functional difference between settled and allSettled enough to not go with the simple option.

Promise.settled -> Promise.raced?

Whatever we choose, it should be an infinitive.

Oh, I see what @devsnek is saying. Promise.settle sounds good to me.

settle to me sounds like it's forcing the promises to be settled, which isn't the case :-/

both settle or settled seems better than allSettled for me.

There's two precedents here: adjective all, and verb/noun race.

settled or allSettled align with the all precedent.

Another suggestion: resolveSettled(..) or resolveAll(..)

My thinking is, resolve(..) means "create a promise that is (eventually) 'resolve'd to the state of the promise in question."

With resolveSettled(..), it means "create a promise that is (eventually) 'resolve'd to the 'settled' states of all promises."

With resolveAll(..), it means "create a promise that is (eventually) 'resolve'd to the settled states of 'all' promises."

We're definitely not misusing the term "resolved" for this method.

For one, you're deliberately making a claim -- that resolve() creates confusion or is "misused" -- without any backing for it. And policing this process -- a thread which is supposed to be about opinions -- as if your opinion is somehow more valid than my opinion.

Secondly, my attempts to share opinions are guided from my role as educator. I think that's a valid and necessary voice in the TC39 process. So I don't appreciate a tone that tries to shut out that perspective as invalid.

@getify resolved and settled do actually mean different things though. a promise can be resolved but not settled if, for example, it was resolved with another promise.

@devsnek I already addressed that concern in an earlier message of this thread. From the educator's perspective -- that is, how learners learn and use these features -- I don't think that's a meaningful distinction. Introducing "settled" alongside "resolved" because of the spec-author's perspective probably feels "academically correct" but is, I think, broadly not a good move for how JS is learned and used.

I agree that most developers probably don’t need to know the difference, @getify. However, when designing a new API, we should stick to the same terminology that’s been used since the standardization of ECMAScript Promises began. β€œSettled” is not a new term.

@mathiasbynens JS shipped the word resolve(..) to mean, ostensibly to most new learners, a "promise that's finished (aka 'settled')". Most example code for promise constructors used res or resolve for the name of the function callback that does resolution. It's notable that these examples didn't use ful or fulfill. Why? Because that function doesn't just "fulfill", it actually invokes the JS-spec-named "promise resolution" algorithm, as does Promise.resolve(..). Many authors wrote about JS, like myself, and used "resolve" to describe this process of "finishing" a promise (either with fulfillment or rejection).

Given that context, I don't think "settle" has any more established precedent here. Yes, it was used in user-land libs, but that terminology was never exposed by JS itself; "resolve" was.

I can't imagine having ever taught what Promise.resolve(..) does, and saying to a learner, "Promise.resolve creates an eventually settled promise." Not only is the "eventually" part an unnecessary/confusing implementation detail for them at that point, but why is it a "settled" promise if the function that creates it is called "resolve"?

The most natural thing has been to teach it as an "already resolved promise." I stand by that description. Many others have, as well. For TC39 to try to deliberately depart from that precedent is a mistake, I think.

Whether or not the average user knows or cares about the difference, using colloquial terminology when defining a language is probably not the best direction to take.

What would this hypothetical user think about β€œresolve” after seeing/understanding Promise.resolve(Promise.reject())?

@ljharb Were I to teach that sort of pattern -- I don't, because I don't think there's any point to it, but... -- I would describe it as "Make a new resolved promise from that rejected promise." Because I've already taught that "resolved" promises can either be fulfilled or rejected, I don't think that would be confusing at all.

And Promise.resolve(new Promise(() => {}))?

(I’m going to assume your often-repeated claims that it’s better to teach the warts than to ignore them means you would want to teach both of these patterns)

using colloquial terminology when defining a language is probably not the best direction to take.

I'm not talking about what language to use inside the spec, that's definitely not my purview.

I'm talking about opinions on API design aesthetics. Using academically correct names is generally preferable, unless it departs from expected norms for the users, in which case the "colloquial" terminology is preferable in that it captures and communicates what the developer thinks and means, rather than what the spec authors are thinking.

Similar to the "Priority of Constituencies" principle from the HTML spec, if those two perspectives are ever at odds, the concerns of the end developer should be more paramount than the concerns of the spec author.

When the colloquial term would give a user an incorrect mental model, I’d argue we have a duty to the user to introduce a new term to help clarify it.

@ljharb Promise.resolve(new Promise(() => {})) is even more bizarre than Promise.resolve(Promise.reject()). I can't imagine particularly legitimate scenarios for either, but I would argue the former is actually harmful and should be avoided at all costs.

Yes, I generally like to teach the warts, but only if they're instructive to folks on what they should be doing.

In that sense, I'd only teach Promise.resolve(new Promise(() => {})) if my point was to teach them about the dangers of having never-resolved promises, and in particular why they might want to use Promise.race(..) with a timeout-promise to avoid forever-hanging.

So to your question, aside from academic JS quizzes for job interviews (which are harmful to the community, btw), were I to show such a dangerous -- dangerous in the sense of actively inviting pit-of-failure design, harder to debug/read/understand code, etc -- piece of code, I would describe it as such:

"Create a promise that will never resolve." I think that's entirely consistent with the mental model I've taught with and have presented thus far in this thread.

When the colloquial term would give a user an incorrect mental model

Except that I dispute that it's an incorrect mental model. I think it's the most helpful and pragmatic mental model. I haven't heard a single use-case for why distinguishing between "promises that may eventually settle" and "promises that are already resolved" in the API design of JS has any semblance of best practice in teaching.

Not that my teaching methods are on trial here, but since it's been called into question... to further explain the mental model I use for teaching promises in JS, I teach that promises are intended to normalize value interactions in a time-independent fashion. That is, you shouldn't be thinking or asking "is it finished yet or not", you should be saying, "whenever it's finished, this is what you do next."

As such, distinguishing between a resolved (but not settled) promise and a settled promise are actively counter to that mental model, would be (I think) obstructive to the learning of most developers, and should most definitely not be codified into the API design of the language.

hax commented

@getify I agree some may use "resolved" in which they really mean "settled". But unfortunately I see much more use "resolved" in which they really mean "fulfilled", for example jQuery use "resolved" for "fulfilled" in all APIs/documentation and refuse to fix it.

It's not only jQuery. I talked to some educators in Chinese JS community to investigate this problem, and we have consensus that many understand "resolved" as "fulfilled" because of:

  1. The APIs are Promise.resolve vs Promise.reject, and new Promise((resolve, reject) => ...), so it's very easy to treat resolved/rejected as a duality instead of fulfilled/rejected (yes there is promise.then(onFulfilled, onRejected), but in most cases, programmers only use promise.then(callback))
  2. Both resolved and rejected start with re prefix and both are 8 letters, so as intuition/aesthetic, programmers paired resolved/rejected instead of fulfilled/rejected πŸ˜‚
  3. It seems in most cases programmers just resolve to a normal value so you can use fulfill interchangeably, and rarely resolve to another promise/thenable

To be wise after the event, we may reform the promise APIs to Promise.fulfill(value) (throw TypeError if value is promise/thenable), Promise.delegate(thenable) and Promise.reject(reason) which can solve "resolved" confusions. But it's too late...

Anyway, I think it's not a good idea to use resolve in this API to add new chaos to unresolvable resolved issue 😝


On the other hand, I agree "settle" is not a well-known word, for example, original Promise A+ spec never use "settle". And some libraries like jQuery (jQuery again! πŸ€ͺ) never use word "settle" too.

So here are alternavites I can imagine:

  • Promise.finally(), Promise.finallyAll (if this, I'd expect Promise.prototype.finally to be upgraded to also receive {status, value} object from the callback)
  • Promise.finished(), Promise.allFinished() (it seems "finish" is a much colloquial word than "settle" and hopefully do not have confusion like "resolve")
  • Promise.complete(), Promise.completed(), Promise.allComplete(), Promise.allCompleted() ("complete" from old jQuery API)
  • Promise.fulfilledOrRejected(), Promise.allFulfilledOrRejected() (wordy, but at least easy to understand and match the spec terminology πŸ€“)

Anyway, I think it's not a good idea to use resolve in this API to add new chaos to unresolvable resolved issue

What you call chaos, an educator calls consistency. The points you make IMO are far more in favor of my position than against it.

Whether it's right or wrong, if people have come to believe a word means (or is related to) something, the best thing to do is not try to "outsmart" them by correcting (only) new APIs to a more accurate term, while leaving existing ones untouched -- inconsistency breeds confusion in learners. Rather, you gently pave the cowpaths and fix up whatever you can behind the scenes to keep them from tripping.

I think the conflation people may have with "resolve" and "fulfill", while regretable from a design standpoint, is not the serious concern it's being made out to be here.

If they don't often consider any such distinction, there's never any problem at all. If they stop to consider the distinction, it's only a small hiccup and the dissonance quickly passes.

In practice I don't forsee distinguishing between the two as being a mainstream common concern, certainly not to the extent where this conflation will block or fail often.

By contrast, insisting on changing the terminology will actually call attention to the naming conflation, and will cause more turbulence/confusion. Unnecessarily, I claim.

hax commented

@getify What I mean "add new chaos" is:

Consider some unfortunate programmers (from jQuery land πŸ€ͺ) who treat resolve as fulfill, how they understand resolveAll() method as you suggested?

They may treat it as fulfillAll() or allFulfilled(). But Promise.all() is just allFulfilled()! So they realize they may understand resolve in the wrong way. Then they read the manual, articles and finally reading the fuxxing spec. Then they understand what resolve really mean (and know another reason why they should drop jQuery 🀣) , and Ooops, they find that resolveAll is a WRONG name as spec, so they go to this repo (or tc39/ecma262 repo if already stage 4) and post an issue: "resolveAll() should be settleAll()!" ...

Hope u can get me.

What about Promise.all.continue() and Promise.all.break()* ?

* Promise.all() would be a shorthand of Promise.all.break()

What about an option object for the second parameter of Promise.all ?

e.g. Promise.all(array, { settle: true });

hax commented

@Mouvedia something all break seems not a good name πŸ€ͺ

Promise.all(array, { settle: true }) doesn't solve the problem because the problem (unfamiliar term "settle") still there, and how we interpret Promise.all(array, { settle: false })?

@hax the idea is the second parameter, the name of the boolean property is up to you all. The default could be true or false depending on the name chosen.

The option object lets us have even more granularity; for example someone may want to have the opposite of all which would short-circuit on fulfil or all promises have rejected. We don't know what we might add later.

hax commented

@Mouvedia Overload one method for some different usage may be not a good thing. Note the shape of the return array in Promise.all and Promise.allSettled are different.

Note the shape of the return array in Promise.all and Promise.allSettled are different.

In what way?

hax commented

In what way?

Promise.all returns array of fulfilled values, or the first rejected reason
Promise.allSettled returns array of {status, value}

If we did pick the option object we would have to normalize the returned array somehow. It's not practical though so that proposal (second argument) is not retained.

something all break seems not a good name

@hax well I don't wanna introduce Promise.all.<foo>() without its pendant. Promise.all must be retroactively made the shorthand. I wanted to reuse keywords that were familiar to JS developers and made sense: break or continue on rejection.

hax commented

@Mouvedia At least I don't get it directly why it's break or continue... Only after second thought, I realize you mean what happen when rejected. But I think most programmers won't consider negative path first.

To me, the most simple word is allFinished, see the last paragraph of my previous comment: #3 (comment)

I've never read the Promise spec directly. My knowledge comes from reading terse documentation and looking at code examples. And @hax has a point, 'in the wild' people use the term "resolved" to indicate "a promise that has been fulfilled". Actually, I'd never even seen the term "fulfilled" in relation to promises until I stumbled on this issue!

As stated before, I believe the confusion is compounded by the fact that by convention people use new Promise((resolve, reject) => {}); and the API calls are Promise.resolve and Promise.reject which seem to correlate (at first glance/high level) to the same "fulfilled"[1] and "rejected" callbacks. I've always treated the two API calls as methods to instantly create new "fulfilled"[1] or "rejected" promises.

If we take the API call Promise.resolve(thenable), again something I didn't know was even valid until this issue, then it makes sense why "resolved" != "fulfilled". As Promise.resolve(Promise.reject('foo')) returns a rejected promise. If the API call was named Promise.fulfill and didn't allow thenables then perhaps there would be less misused terminology, I dunno.

With my new knowledge, I think this stems from poor choice of keywords in the initial spec. Of course, it's difficult to predict how words are going to be used in the future; even in every day language definitions change, words are lost, and new ones are created. My point is, there's no way to stop people from using the incorrect terms. It's a losing battle to try and fight that.

Adding a method Promise.resolveAll, with what I feel is commonly diffused terminology, sounds as if the function is either going to "make all of these promises fulfilled" or have the same functionality as Promise.all. On the other hand, when I saw Promise.allSettled I felt like I almost immediately knew the purpose and could correlate it's actions with it's name. Promise.settleAll also works. I'm not sure about Promise.settle as it doesn't quite convey that it takes an array. In reality, the singular version sounds like what Promise.resolve(thenable) should have been (in which the function is split into two functions, one that takes a thenable which flattens it and another that creates a fulfilled promise).

[1] Where in my head I have (had?) the assumption "resolved" = "fulfilled".

We've briefly discussed the naming issue at the September 2018 TC39 meeting (I had a slide about it), and there was consensus on using settle in the name. I'll mention this again at this month's TC39 meeting and will attempt to get this consensus explicitly recorded in the meeting notes.

Given that "settled" is the only correct term for a promises that is not pending (i.e. what this proposal is about), and that the name allSettled in particular has strong precedent in userland libraries, I'm proposing we stick with allSettled. It doesn't make sense to use an incorrect term, or to invent a new term for the same thing.

Being settled is not a state, just a linguistic convenience.

But by all means, let's design APIs as if it's an undisputed scientifically precise term!

Promise.all
Promise.race
Promise.reject
Promise.resolve
Promise#catch
Promise#finally
Promise#then

The current API is clean. Could we be consistent and keep the one word convention?

e.g.

/*
  Promise.every(iterable, ?options = {
    fulfilled: true,
    rejected: true
  })
*/

// default behaviour
const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];
const results = await Promise.every(promises);
const successfulPromises = results.filter(p => p.status === 'fulfilled');

// all must be rejected (doesn't have an equivalent in the current proposal)
Promise.every(promises, { fulfilled: false })
  .then(everyPromiseRejected, atLeastOneWasFulfilled)

// all must be fulfilled (equivalent of Promise.all)
Promise.every(promises, { rejected: false })
  .then(everyPromiseFulfilled, atLeastOneWasRejected)

or

/*
  Promise.every(iterable, status => bool)
*/

// the default predicate function would be
function isSettled(s) {
  // s is [[PromiseStatus]]
  return s === 'fulfilled' || s === 'rejected';
  // or
  // return s !== 'pending';
}

addendum: @getify proposed Promise.every earlier

Some ideas that come to mind without strong opinions of my own: Promise.once([… /* immediately resolve once those promises settle and … */]) and Promise.when([… /* when those promises are settled do … */]) β€” Intuition (my own) may associate when with customElements.when, and once with one-time listeners, so once feels more immediate, when somewhat more passive in comparison, but far less passive than Promise.all(…).finally(…) (imho).

I guess the important thing to mention is naming this operation will likely see less confusion or distortions if its actual subject is qualified as the Promise.all([…]) => promise instance (more intuitive imho) compared to when considering naming a member that hangs of the Promise constructor just because it does and probably should by all means. I may be absolutely completely wrong about all this, thoughts?

Thanks for the feedback, everyone!

Per consensus in today’s TC39 meeting, we’re moving forward with the name allSettled.

late to the shed, but would have suggested allFates since we're really looking for the states and fates

@IDisposable No, we’re looking for 2 out of the 3 states, per the document you pointed to.

IMHO Promise.every(Iterable, predicate = status => bool) would have been a much better choice.

I wish this was less privy to the TC39 and that this kind of issue had been presented to a much larger panel of users.

Promise.every() would be confusing with Promise.all(), I'm sure this would not be a problem to experienced programmer, but to newbie, it would be a headache. An explicit name would be good enough. I'm good with .allSettled()