GithubHelp home page GithubHelp logo

Comments (19)

rpamely avatar rpamely commented on August 16, 2024 4

I find it quite surprising to never get a rejection here

If I understand correctly, you are suggesting that if some, but not all, promises reject the resulting promise should be fulfilled, but if all reject the resulting promise should reject? I think this would be quite annoying to code against because you would have to write a handler for both the fulfilled and reject case, instead of just fulfilled.

Needing to check if all of the input promises were rejected is just one possible use case.

from proposal-promise-allsettled.

rpamely avatar rpamely commented on August 16, 2024 1

So I am not saying you would not need to catch errors. I think the difference is that with Promise.all for example, your error handling is very different from your success logic.

With Promise.allSettled you may want to do exactly the same handling depending on whether some, all or no promises reject. You may end up duplicating your code in both handlers. It's not clear to me that all promises rejecting is such a special case that it should be handled differently, and I think it makes it harder to learn.

from proposal-promise-allsettled.

jasonwilliams avatar jasonwilliams commented on August 16, 2024 1

I agree with @ljharb, having it reject when all promises reject only makes it more complicated in the long run.

A scenario would be that i'm not sure if some APIs will fail or not, or even if all fail, so to make sure i would need to duplicate my handler in both fulfilled and rejected which does feel like a code smell.

Promise.all is different in this case as the rejected branch is handled differently to the "everything's working" branch, but its not really the same case for allSettled.

I think that's going to create some confusion in the community.

I'm not so sure, this has been the standard pattern (implemented in userland) for quite some time. All libraries have implemented it in the same way, and they get quite high usage.

from proposal-promise-allsettled.

ljharb avatar ljharb commented on August 16, 2024

I think that what you're describing is a different combinator - I wouldn't expect to get a rejection ever here. The promise returned here would be "fulfilled" when all promises have a resolution, regardless of what that resolution was (fulfillment or rejection) - in other words, it shouldn't be possible for this combinator to ever reject.

from proposal-promise-allsettled.

getify avatar getify commented on August 16, 2024

I think that what you're describing is a different combinator

I'm not proposing a different combinator. I'm proposing that this one change, because I think this one, as described, will be troublesome by default. But again, if you want it as described, it's trivial to do so with finally(..) instead of then(..).

I wouldn't expect to get a rejection ever here.

I find it quite surprising to never get a rejection here, and I think others will, too. Promise.all(..) can reject, so it doesn't seem surprising at all that this helper could reject, too.

We obviously disagree, but I'm just basing my reaction on how it felt to read the proposal, and then me imagining how this is going to be taught, compared to the stuff about promises I already teach, and the questions people are going to likely ask. It's not common, in my experience, for APIs to turn promise rejections into fulfillments.


Moreover:

  1. Figuring out if all were rejected regrettably requires iterating through all results. By contrast, deciding that with my proposed change is simple O(1), by catching the rejection.
  2. In my proposed change, you cover more bases:
    • you trivially serve the original motivating use-case with finally(..)
    • you serve the use-cases/perspective I've brought up with then(..) / catch(..)

API design that serves more use-cases with no additional complexity (only having to pick a different chain method) should be worth at least considering.

from proposal-promise-allsettled.

ljharb avatar ljharb commented on August 16, 2024

.finally provides no argument to its callback, so you couldn't use it to get at the array of items - and imo the originally motivating use case isn't just "wait til they're all settled", it's "i want the results of them all being settled".

from proposal-promise-allsettled.

getify avatar getify commented on August 16, 2024

If you want to know only that all promises settled (all rejected or not), you could just as easily use .finally( fn ) as .then( fn ), and that would be true with or without my proposal. In fact, finally(..) has a better semantic in the "just let me know they finished" base case, so it's probably slightly better anyway.

If you want the results of all of them (all rejected or not), .then( fn, fn ) doesn't seem that much more onerous than the equivalent .then( fn ) in the current proposal. Of course, that would discourage inline function expressions in your chain in that case, but I personally think that should be discouraged (not disallowed) anyway.

from proposal-promise-allsettled.

getify avatar getify commented on August 16, 2024

you would have to write a handler for both the fulfilled and reject case, instead of just fulfilled.

You have to do that for Promise.all(..) and Promise.race(..), and in general should have a .catch(..) on all promise chains, no matter what, because accidental exceptions being swallowed is such an insidious program bug that it must be avoided at all costs.

I don't think having to write a handler for both cases is a bad thing, it's exactly the best practice you should already be doing for almost all other usages of promises.

from proposal-promise-allsettled.

getify avatar getify commented on August 16, 2024

you may want to do exactly the same handling depending on whether some, all or no promises reject.

If it's the case that you want to do "exactly the same handling", you should just pass the same handler for both positions, as I said in an earlier message:

.then( handler, handler )

I don't know how common "exactly the same handling" will be, but in whatever cases it would be, specifying the same handler function twice, rather than duplicating the code, does not seem onerous.

As a matter of fact, if you truly want "exactly the same handling" in both situations, I think it's preferable to list the same handler twice, so it's obvious that you're doing that, rather than relying on Promise.allSettled(..) collapsing both cases into a single state.

from proposal-promise-allsettled.

ljharb avatar ljharb commented on August 16, 2024

Using the second then argument is a code smell outside of a few specific cases, and we shouldn’t be encouraging it’s use - especially since the handler function is often created inline, and isn’t available in a variable.

The only behavior that i think can possibly make sense here is either:

  1. always fulfills when all resolve
  2. fulfills when all fulfill, rejects when any or all reject

But since the “some reject some fulfill” case needs to differentiate fulfillment and rejection in any outcome, both of the two above behaviors would need to, and so i don’t see the utility in the second - especially considering that a common use case for me will be racing it against a rejected promise, such that a rejection would only mean a timeout.

from proposal-promise-allsettled.

getify avatar getify commented on August 16, 2024

Using the second then argument is a code smell

I don't think there's any objective standard that's established this, at least not strongly enough that it should guide language API design.

But even if most people really did feel that way, it's well established "best practice" in the community to "always have a catch(..)" instead of the second .then(..) argument, so if you prefer, do .then(handler).catch(handler). That's well in line with typical practice.

a common use case for me will be racing it against a rejected promise, such that a rejection would only mean a timeout.

That is indeed a common use-case and as far as I can tell well served by Promise.race(..). It seems to me that use-cases for allSettled(..) are quite different from "I want to know which finishes first, a promise I care about or a rejection-timeout". That is, allSettled(..) wouldn't give you any information about the timing of which was settled first, so it offers nothing for that use case, AFAICT.

from proposal-promise-allsettled.

ljharb avatar ljharb commented on August 16, 2024

I think I wasn't clear. My use case for allSettled is "i want to know when all of these promises are settled, and then work with the resulting list of resolution values or rejection reasons" - I'd also use Promise.race to ensure that I wasn't waiting forever. That means my preferred two channels (then vs catch) are "they all settled" vs "the allSettled promise lost the race". Making allSettled ever reject makes that much harder.

from proposal-promise-allsettled.

getify avatar getify commented on August 16, 2024

Making allSettled ever reject makes that much harder.

Would this work?

Promise.race([
   timeoutPromise,
   Promise.allSettled([ .. promises .. ]).catch(x=>x)   // <-- turn rejected to fulfilled
]);

from proposal-promise-allsettled.

ljharb avatar ljharb commented on August 16, 2024

Indeed it would, but I’m still not seeing the utility of forcing users to do either that, or to pass two handlers.

from proposal-promise-allsettled.

getify avatar getify commented on August 16, 2024

but I’m still not seeing the utility

Let me restate my reasons for this stance:

  1. I think it's going to be more confusing to learners to have a promise API method that paves over rejections and turns them into fulfillment, when compared to the others. I am imagining a student in a workshop quite plausibly asking, "Why do Promise.all(..) and Promise.race(..) result in a rejection if there are rejections, but Promise.allSettled(..) never rejects?"

    Especially when you consider Promise.all(..). Set aside the short-circuting for just a moment, because I find most users don't really think about that part when considering how Promise.all(..) works. What they think is, "I get a fulfillment if all pass, or a rejection if any fails." In that mental construct, they would likely expect a similar/symmetric behavior with allSettled(..).

    They might expect a rejection if any rejections occur, but that isn't even what I'm asking for here. I'm only saying that a rejection happens in the case that all of them rejected. That seems like it would be very reasonable to explain and teach. Currently spec'd behavior feels more exceptional and harder to explain/understand.

  2. In the case where I really want to distinguish between the two cases of "some rejected" and "all rejected" -- which I claim will be a common use-case -- in the currently spec'd behavior, that's "harder" because the only way to distinguish is to iterate through all of them and check. It becomes trivial to distinguish those two cases if we have fulfillment vs rejection as the signal.


My argument is first, (1) and (2) are compelling to consider. And second, since the drawbacks, as discussed now in a dozen messages of this thread, seem like less severe and addressable, the arguments against, while understandable, aren't as compelling.

from proposal-promise-allsettled.

ljharb avatar ljharb commented on August 16, 2024

Your reasoning is compelling in terms of comparison to Promise.all - however, if it only rejects when all reject, then i think you'd have the same confusing teaching scenario: trying to teach when it rejects and when it doesn't. Following your logic, I'd expect a rejection when any reject, just like Promise.all (i still prefer always fulfilling, ofc)

from proposal-promise-allsettled.

getify avatar getify commented on August 16, 2024

trying to teach when it rejects and when it doesn't. Following your logic, I'd expect a rejection when any reject, just like Promise.all (i still prefer always fulfilling, ofc)

Indeed, they might expect that at first.

How I'm imagining this to be reasoned about, documented, taught, etc, is: "Unlike Promise.all(..), allSettled(..) doesn't short-circuit, so it's only a rejection if all reject, as signified importantly by the 'all' in the name."

I'm just saying, that teaching description seems more reasonable than what would be required for how it's currently spec'd -- which might be something like, "Unlike Promise.all(..), allSettled(..) never rejects because rejections are normalized as settlements in a fulfillment state.", or whatever.

from proposal-promise-allsettled.

getify avatar getify commented on August 16, 2024

What you're both saying is that essentially you don't agree that it matters that distinguishing "some rejected" vs "all rejected" is much more complicated
/inefficient as a result of the spec.

My suggestion offers a trivial solution for it, and trivial work-arounds for the cases it doesn't handle directly. But rather than addressing the negative tradeoff I'm concerned about, you're basically just dismissing it.

Shrugs. I think I've said all I can on it.

from proposal-promise-allsettled.

ljharb avatar ljharb commented on August 16, 2024

In either case (where it can produce a rejection) if you want to distinguish some rejected vs all rejected, you have to do iteration for one of the cases. In the case where it never rejects, you always have to iterate. You’ve made a pretty good argument i think for “it can reject”, but imo the stronger case there is for “it rejects when any reject”, like Promise.all.

from proposal-promise-allsettled.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.