GithubHelp home page GithubHelp logo

tc39 / proposal-promise-allsettled Goto Github PK

View Code? Open in Web Editor NEW
314.0 28.0 27.0 98 KB

ECMAScript Proposal, specs, and reference implementation for Promise.allSettled

Home Page: https://tc39.es/proposal-promise-allSettled/

HTML 100.00%

proposal-promise-allsettled's Introduction

Promise.allSettled

ECMAScript proposal and reference implementation for Promise.allSettled.

Author: Jason Williams (BBC), Robert Pamely (Bloomberg), Mathias Bynens (Google)

Champion: Mathias Bynens (Google)

Stage: 4

Overview and motivation

There are four main combinators in the Promise landscape.

name description
Promise.allSettled does not short-circuit this proposal πŸ†•
Promise.all short-circuits when an input value is rejected added in ES2015 βœ…
Promise.race short-circuits when an input value is settled added in ES2015 βœ…
Promise.any short-circuits when an input value is fulfilled separate proposal πŸ”œ

These are all commonly available in userland promise libraries, and they’re all independently useful, each one serving different use cases.

A common use case for this combinator is wanting to take an action after multiple requests have completed, regardless of their success or failure. Other Promise combinators can short-circuit, discarding the results of input values that lose the race to reach a certain state. Promise.allSettled is unique in always waiting for all of its input values.

Promise.allSettled returns a promise that is fulfilled with an array of promise state snapshots, but only after all the original promises have settled, i.e. become either fulfilled or rejected.

Why allSettled?

We say that a promise is settled if it is not pending, i.e. if it is either fulfilled or rejected. See promise states and fates for more background on the relevant terminology.

Furthermore, the name allSettled is commonly used in userland libraries implementing this functionality. See below.

Examples

Currently you would need to iterate through the array of promises and return a new value with the status known (either through the resolved branch or the rejected branch.

function reflect(promise) {
  return promise.then(
    (v) => {
      return { status: 'fulfilled', value: v };
    },
    (error) => {
      return { status: 'rejected', reason: error };
    }
  );
}

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

The proposed API allows a developer to handle these cases without creating a reflect function and/or assigning intermediate results in temporary objects to map through:

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

Collecting errors example:

Here we are only interested in the promises which failed, and thus collect the reasons. allSettled allows us to do this quite easily.

const promises = [ fetch('index.html'), fetch('https://does-not-exist/') ];

const results = await Promise.allSettled(promises);
const errors = results
  .filter(p => p.status === 'rejected')
  .map(p => p.reason);

Real-world scenarios

A common operation is knowing when all requests have completed regardless of the state of each request. This allows developers to build with progressive enhancement in mind. Not all API responses will be mandatory.

Without Promise.allSettled this is tricker than it could be:

const urls = [ /* ... */ ];
const requests = urls.map(x => fetch(x)); // Imagine some of these will fail, and some will succeed.

// Short-circuits on first rejection, all other responses are lost
try {
  await Promise.all(requests);
  console.log('All requests have completed; now I can remove the loading indicator.');
} catch {
  console.log('At least one request has failed, but some of the requests still might not be finished! Oops.');
}

Using Promise.allSettled would be more suitable for the operation we wish to perform:

// We know all API calls have finished. We use finally but allSettled will never reject.
Promise.allSettled(requests).finally(() => {
  console.log('All requests are completed: either failed or succeeded, I don’t care');
  removeLoadingIndicator();
});

Userland implementations

Naming in other languages

Similar functionality exists in other languages with different names. Since there is no uniform naming mechanism across languages, this proposal follows the naming precedent of userland JavaScript libraries shown above. The following examples were contributed by jasonwilliams and benjamingr.

Rust

futures::join (similar to Promise.allSettled). "Polls multiple futures simultaneously, returning a tuple of all results once complete."

futures::try_join (similar to Promise.all)

C#

Task.WhenAll (similar to ECMAScript Promise.all). You can use either try/catch or TaskContinuationOptions.OnlyOnFaulted to achieve the same behavior as allSettled.

Task.WhenAny (similar to ECMAScript Promise.race)

Python

asyncio.wait using the ALL_COMPLETED option (similar to Promise.allSettled). Returns task objects which are akin to the allSettled inspection results.

Java

allOf (similar to Promise.all)

Dart

Future.wait (similar to ECMAScript Promise.all)

Further reading

TC39 meeting notes

Specification

Implementations

proposal-promise-allsettled's People

Contributors

chicoxyzzy avatar ewanharris avatar gibson042 avatar grossacasac avatar gsathya avatar jasonwilliams avatar ljharb avatar mathiasbynens avatar rpamely avatar xtuc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

proposal-promise-allsettled's Issues

Editorial nit: add more !s

In more modern parts of the spec, we're trying to use !s before operations we know will not fail, in a consistent way. In fact we're generally trying to have every abstract op call be preceeded by either ! or ?, if we're not treating the result directly as a completion value.

The place where this stuck out the most to me was ObjectCreate(%ObjectPrototype%), and CreateDataProperty(), since those are things that could fail under normal circumstances but in the specialized circumstance of this spec never can.

But other abstract op invocations seem like they would also benefit, e.g. CreateArrayFromList(), CreateBuiltInFunction().

Note that the IteratorX() calls do not need this treatment, because you treat their return values as completions, so that is already working as intended.

Poisoned `then` property rejecting the returned promise

While I'm writing Test262 tests, I found this odd behavior using a poisoned `then .

See examples here: https://gist.github.com/leobalter/d5ddfd403c0b92333201ea7f8872d5a4

PerformPromiseAllSettled will return an abrupt from a poisoned then method, which will cause Promise.allSettled to reject the returned promise. It sounds like it should handle this as a rejection in the settled list.

Specs today:

var assert = require('assert');

var promise = new Promise(function() {});
var error = new Error();

promise.then = function() {
  throw error;
};

function $DONE(arg) {
  assert.strictEqual(arg, undefined);
  console.log('ok');
}

Promise.allSettled([promise])
  .then(function() {
    throw 'The promise should be rejected';
  }, function(reason) {
    assert.strictEqual(reason, error); // this is the current spec text today
  }).then($DONE, $DONE);

I'm using the $DONE function to simulate test262.

I'm not sure this is the desired behavior, it sounds like it would be a bit more consistent to have something like allSettled fulfilling its returned promise with the settled promises:

Promise.allSettled([promise])
  .then(function([ { status, reason } ]) {
    assert.strictEqual(status, 'rejected');
    assert.strictEqual(reason, error);
  });

Please let me know how to follow from this. A test matching the spec text should be available soon in Test262.

Misspelling "fullfilled"

Ie, if you don't immediately recognize that I misspelled "fulfilled" in the title, you've written a bug.

Can it be "resolved"? IMHO it will have much less misspellings.

The return type is not very friendly to parameter destructuring

In many cases, when a developer use Promise.all or Promise.allSettled, they "believe" everything will be OK, exception handling is not always required

Current definition of the return type [{status, value}, {status, reason}] is not very friendly to parameter destructuring, for example when we async import 5 modules at a time:

const modules = ['./a', './b', './c', './d', './e'];
const [{value: a}, {value: b}, {value: c}, {value: d}, {value: e}] = await Promise.allSettled(modules.map(m => import(m));

It is obviously too redundant and hard to read

We may change the return type from {status, value | reason} to [value | reason, status] so that we can destructure it like:

const modules = ['./a', './b', './c', './d', './e'];
const [[a], [b], [c], [d], [e]] = await Promise.allSettled(modules.map(m => import(m));

"JavaScript programmers can currently accomplish the same thing through this API"

JavaScript programmers can currently accomplish the same thing through this API

This is a confusing sentence. It implies:

  1. This API already exists and is usable
  2. This API is specific to "JavaScript" (isn't this all JavaScript?)

Maybe it should be changed to something like,

The proposed API allows this simplified form:

πŸ™‚

P.S. I realize maybe this was referring to the reference polyfill, but if so then it should be linked directly. It wasn't clear to me what was meant on the first read.

Behavior with all "settled" promises as rejected

If I'm understanding the proposal correctly, all input promises being rejected still results in a fulfilled promise, correct?

I think that's going to create some confusion in the community. Paving over all rejections and turning them into a fulfillment, regardless of the use-cases you may want that, I think is troublesome to teach and get your head around. Also, if I want to answer "were they all rejected?", I think the only way is to manually iterate all the results, right?

But I also understand why the short-circuting of Promise.all(..) to a rejected promise, as soon as any single rejection is encountered, is not helpful, either.

I think there's a tweak to this proposed behavior that will make more sense, allow better ergonomics in case of need to distinguish, and still provide the same facility as is desired here.

I suggest, in the case of all rejections, this method's promise actually resolves to rejected, again with an array of all results. That way, the mental model is, "if any results are fulfilled, you know that by a fulfilled promise" and "if all results are rejected, you know that by a rejected promise". IOW, you can easily distinguish those two cases.

In the case where you don't want to distinguish, and only care about "all resolved no matter what", then you can just use finally(..) instead of then(..), and you get that desired result.

A flag in addition to (or rather than) `status`?

#26 raised the concern that it's easy to misspell "fulfilled". A separate usability issue is having to compare status with a string, which is a bit verbose.

Perhaps a simple boolean flag instead? Or in addition? (Like ok on fetch responses, which is in addition to status.) Promises aren't going to get a third settled state after all, it's just fulfilled and rejected.

The obvious name for the flag is fulfilled, which helps with the verbosity but not with spelling.

Taking a page from fetch, perhaps ok? I realize it projects a value judgement on fulfillment vs. rejection, but it's one I think all but extreme purists would be okay with (no pun).

So a fulfilled promise would look like:

{status: "fulfilled", ok: true, value: /*...*/}

and a rejected one:

{status: "rejected", ok: false, reason: /*...*/}

(Unless status is removed, but to me adding ok without removing status is the better option.)

Pros:

  • Solves the spelling issue
  • Provides a concise way to check

Cons:

  • Interop footgun wrt external Promise libraries like Q that just use status?

If folks are happy with the idea (or some version of it that gets consensus), I can make the (trivial) spec changes and do a PR.

Return type

The readme leaves this as an open question; it seems to me like we should figure this out by Stage 2.

Some thoughts about the two options:

  • For the object option, I'm not sure what those three properties would be. I can see two properties that you would want: one to distinguish resolve and reject, and one for the value. I'm not sure what the reason would be, apart from the value.
  • For a Maybe type, it seems like we're really talking about an Either type, since the Nothing type needs to store the reason, right?

A third option: return the same array of Promises; to read whether they are resolved or rejected, use then. I am not sure if this would be too awkward, but it would be consistent with other Promise methods in a way, and avoid adding anything.

Compatibility with existing Promise subclasses

Assume an existing coherent Promise subclass that does not define an allSettled method. Would the addition of this intrinsic break some aspect of its coherence? And if so, should that be remedied?

I believe that we should strive to avoid slot access that break existing subclasses, and this spec text appears to be safe (it adopts every argument with the receiver's resolve and registers its own handlers with the resulting value's then). But I would like both the goals and their fulfillment to be given a proper treatment.

Bikeshedding about name

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

Missleading sentence

Due to the short circuit nature of Promise.all() any rejected promise will cancel the entire operation and return a rejection.

Cancel is miss-leading, especially nowadays with AbortController that exist. I propose to rephrase like :

Due to the short circuit nature of Promise.all() any rejected promise will make the resulting promise reject while silently discarding other results.

Initial spec review

@jasonwilliams and @rpamely wrote the initial spec (#14). πŸ‘πŸ»

Along the way, they ran into some things that were unclear in the existing spec, and had some small questions. I’m going to file separate issues for them and collect them all here, so that everyone can learn from the answers.

Advance to stage 4

Criteria taken from the TC39 process document minus those from previous stages:

tc39/test262#2112
tc39/test262#2124

  • Two compatible implementations which pass the acceptance tests

https://github.com/tc39/proposal-promise-allSettled#implementations

Bug tickets to track:

  • Significant in-the-field experience with shipping implementations, such as that provided by two independent VMs

https://github.com/tc39/proposal-promise-allSettled#implementations

tc39/ecma262#1583

  • All ECMAScript editors have signed off on the pull request

TODO

Test262 coverage note

Reviewing the spec text, I was reminded of the extra care we put in to making Promise.all behave reasonably in the face of weird promise subclasses and similar scenarios, e.g. with the [[AlreadyResolved]] internal slot. I think those are pretty exhaustively tested in existing test262, and this issue is about making sure the same is done for Promise.allSettled.

When writing test262 tests, please be sure to test all the same weird edge cases that can occur as with Promise.all---plus the extra ones introduced by the reject element function and its interactions.

(I'm fine if the champions would like to close this issue and roll it into #11, or move it to the test262 repo. But I'm raising it here for visibility to start with.)

Clarify the need for `Function#length` notes

https://github.com/jasonwilliams/proposal-promise-allSettled/blob/b56ce24c0c970627cc2e2853046dd3fa7bf5c3af/spec.html#L102

      <p>**WHY DO WE NEED TO STATE THIS?** The `"length"` property of a `Promise.allSettled` resolve element function is 1.</p>

https://github.com/jasonwilliams/proposal-promise-allSettled/blob/b56ce24c0c970627cc2e2853046dd3fa7bf5c3af/spec.html#L128

      <p>**WHY DO WE NEED TO STATE THIS?** The `"length"` property of a `Promise.allSettled` resolve element function is 1.</p>

Why not `reflect`?

Why expose an allSettled and not a reflect?

Exposing a Promise.reflect or Promise.prototype.reflect sounded to us like a better abstraction in bluebird, which is why our Promise.settle was eventually deprecated and reflect was encouraged.

It composes with .race and other methods and not just with .all and it also composes with things like arrays iterated with a for await loop and an async iterator

Document more use cases

Good to have references to other implementations, but it would be nice to have a little more information inline, including some realistic use cases.

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.