├── .editorconfig ├── .gitignore ├── .nvmrc ├── .travis-github-deploy-key.enc ├── .travis.yml ├── README.md ├── package.json └── spec.html /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{README.md,package.json,spec.html,.travis.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | 3 | dist 4 | 5 | # Installed npm modules 6 | node_modules 7 | 8 | # Folder view configuration files 9 | .DS_Store 10 | Desktop.ini 11 | 12 | # Thumbnail cache files 13 | ._* 14 | Thumbs.db 15 | 16 | # Files that might appear on external disks 17 | .Spotlight-V100 18 | .Trashes 19 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 8 2 | -------------------------------------------------------------------------------- /.travis-github-deploy-key.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tc39/proposal-promise-any/5dadf6e54af38363fe341f75de88901a5c5f06d0/.travis-github-deploy-key.enc -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | after_success: 3 | - $(npm bin)/set-up-ssh --key "${encrypted_13071c2f6e86_key}" --iv "${encrypted_13071c2f6e86_iv}" 4 | --path-encrypted-key .travis-github-deploy-key.enc 5 | - $(npm bin)/update-branch --commands 'npm run build' --commit-message "Update gh-pages 6 | @ ${TRAVIS_COMMIT}" --directory 'dist' --distribution-branch 'gh-pages' --source-branch 7 | 'master' 8 | branches: 9 | only: 10 | - master 11 | env: 12 | global: 13 | secure: zKoQlRbPLo1vDZfbilVgf0z3lMRFvhPI7mwbI1ebWxeA9BIQ+4CquIf6y5/TRW2PUY6Faf8dfqIKoh7rnqaMI/XMoVIWlGMV+SQCyBAdffpoOGyRO5NRZrdeV/MqzTT/iOHANrDc9ymdMrpV5lOywaANQZyD96q6UMurGsBwSF3Wp08o3FykxkCqNmAxiU4Wgo3FxObSfJjy40dXr854zFJ8HwlBNo/D7nmuhGCHD3ss6nNreiZE+vcV3SGce+neiesW0AmC6J6w5qacpf2QQf7as+HxuP4b/vo8gSEM8Dl4nfWs+QEdHVgUu3zte2N7Jg/HpjbQD+3bKVdQi+FAPXN1ykbxSCFlbgXN95ds8gPBbXY1mx/OLoUcggQ8OmU1O9STCvT0Huzhj5gPoxZoWVXbkmPuGt6P1kbOBgXrM5R17d+gZbzrZ0MMTjbJp8kv0cjla7iyYk1FKCK1vpYyi2ph0mrNYBFN8vWFm2HFpAGaYBoVNw9PH6pCg36pr/2fg3rCfFD8ef6a6zDhRS5vKCaUWLcH4i8QQ8KDTwuXQVOe7GjiJp0Hx0S6cwad4LR+jiNe26A6eurmtN5K75WLERWjRRrdne2IIIC77xh5Whgs0uAAFiPK1BAZKX2zSKq4tMkiRaIpKz8FQHUaYNJVeZk6MIONK3GOIJoSw5w9Rs0= 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ECMAScript proposal: `Promise.any` + `AggregateError` 2 | 3 | **Author**: Mathias Bynens, Kevin Gibbons, Sergey Rubanov 4 | 5 | **Champion**: Mathias Bynens 6 | 7 | **Stage**: Stage 4 of [the TC39 process](https://tc39.es/process-document/). 8 | 9 | ## Motivation 10 | 11 | There are [four main combinators in the `Promise` landscape](https://v8.dev/features/promise-combinators). 12 | 13 | | name | description | | 14 | | -------------------- | ----------------------------------------------- | ------------------------------------- | 15 | | `Promise.allSettled` | does not short-circuit | added in ES2020 ✅ | 16 | | `Promise.all` | short-circuits when an input value is rejected | added in ES2015 ✅ | 17 | | `Promise.race` | short-circuits when an input value is settled | added in ES2015 ✅ | 18 | | `Promise.any` | short-circuits when an input value is fulfilled | this proposal 🆕 scheduled for ES2021 | 19 | 20 | These are all commonly available in userland promise libraries, and they’re all independently useful, each one serving different use cases. 21 | 22 | ## Proposed solution 23 | 24 | `Promise.any` accepts an iterable of promises and returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an `AggregateError` holding the rejection reasons if all of the given promises are rejected. (If something more fundamental goes wrong, e.g. iterating over the iterable results in an exception, `Promise.any` returns a rejected promise with that exception.) 25 | 26 | ## High-level API 27 | 28 | ```js 29 | try { 30 | const first = await Promise.any(promises); 31 | // Any of the promises was fulfilled. 32 | } catch (error) { 33 | // All of the promises were rejected. 34 | } 35 | ``` 36 | 37 | Or, without `async`/`await`: 38 | 39 | ```js 40 | Promise.any(promises).then( 41 | (first) => { 42 | // Any of the promises was fulfilled. 43 | }, 44 | (error) => { 45 | // All of the promises were rejected. 46 | } 47 | ); 48 | ``` 49 | 50 | In the above examples, `error` is an `AggregateError`, a new `Error` subclass that groups together individual errors. Every `AggregateError` instance contains a pointer to an array of exceptions. 51 | 52 | ### FAQ 53 | 54 | #### Why choose the name `any`? 55 | 56 | It clearly describes what it does, and there’s precedent for the name `any` in userland libraries offering this functionality: 57 | 58 | - https://github.com/kriskowal/q#combination 59 | - http://bluebirdjs.com/docs/api/promise.any.html 60 | - https://github.com/m0ppers/promise-any 61 | - https://github.com/cujojs/when/blob/master/docs/api.md#whenany 62 | - https://github.com/sindresorhus/p-any 63 | 64 | #### Why throw an `AggregateError` instead of an array? 65 | 66 | The prevailing practice within the ECMAScript language is to only throw exception types. Existing code in the ecosystem likely relies on the fact that currently, all exceptions thrown by built-in methods and syntax are `instanceof Error`. Adding a new language feature that can throw a plain array would break that invariant, and could be a web compatibility issue. Additionally, by using an `Error` instance (or a subclass), a stack trace can be provided — something that’s easy to discard if not needed, but impossible to obtain later if it is needed. 67 | 68 | ## Illustrative examples 69 | 70 | This snippet checks which endpoint responds the fastest, and then logs it. 71 | 72 | ```js 73 | Promise.any([ 74 | fetch('https://v8.dev/').then(() => 'home'), 75 | fetch('https://v8.dev/blog').then(() => 'blog'), 76 | fetch('https://v8.dev/docs').then(() => 'docs') 77 | ]).then((first) => { 78 | // Any of the promises was fulfilled. 79 | console.log(first); 80 | // → 'home' 81 | }).catch((error) => { 82 | // All of the promises were rejected. 83 | console.log(error); 84 | }); 85 | ``` 86 | 87 | ## TC39 meeting notes 88 | 89 | - [March 2019](https://github.com/tc39/notes/blob/master/meetings/2019-03/mar-27.md#promiseany) 90 | - [June 2019](https://github.com/tc39/notes/blob/master/meetings/2019-06/june-5.md#promiseany) 91 | - [July 2019](https://github.com/tc39/notes/blob/master/meetings/2019-07/july-24.md#promiseany) 92 | - October 2019 [part one](https://github.com/tc39/notes/blob/master/meetings/2019-10/october-2.md#promiseany-for-stage-3) and [part two](https://github.com/tc39/notes/blob/master/meetings/2019-10/october-3.md#promiseany-reprise) 93 | - June 2020 [part one](https://github.com/tc39/notes/blob/master/meetings/2020-06/june-2.md#aggregateerror-errors-update) and [part two](https://github.com/tc39/notes/blob/master/meetings/2020-06/june-2.md#aggregateerror-constructor-update) 94 | - [July 2020](https://github.com/tc39/notes/blob/master/meetings/2020-07/july-21.md#promiseany--aggregateerror-for-stage-4) 95 | 96 | ## Specification 97 | 98 | - [Ecmarkup source](https://github.com/tc39/proposal-promise-any/blob/master/spec.html) 99 | - [HTML version](https://tc39.es/proposal-promise-any/) 100 | 101 | ## Implementations 102 | 103 | - JavaScript engines: 104 | - [JavaScriptCore](https://bugs.webkit.org/show_bug.cgi?id=202566), shipping in Safari 14 105 | - [SpiderMonkey](https://bugzilla.mozilla.org/show_bug.cgi?id=1568903), shipping in Firefox 79 106 | - [V8](https://bugs.chromium.org/p/v8/issues/detail?id=9808), shipping in Chrome 85 107 | - [XS](https://blog.moddable.com/blog/xs10/) 108 | - [engine262](https://github.com/engine262/engine262/commit/c68877ef1c4633daac8b58b5ce1876f709c1cc16) 109 | 110 | - Polyfills: 111 | - [core-js](https://github.com/zloirock/core-js#promiseany) 112 | - [es-shims](https://github.com/es-shims/Promise.any) 113 | 114 | - [TypeScript](https://github.com/microsoft/TypeScript/pull/33844) 115 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "test": ":", 5 | "build": "mkdir -p dist; ecmarkup --verbose spec.html dist/index.html --css dist/ecmarkup.css --js dist/ecmarkup.js" 6 | }, 7 | "devDependencies": { 8 | "@alrra/travis-scripts": "^3.0.1", 9 | "ecmarkup": "^3.25.3" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /spec.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | title: Promise.any 5 | status: proposal 6 | stage: 4 7 | location: https://tc39.es/proposal-promise-any/ 8 | copyright: false 9 | contributors: Mathias Bynens, Kevin Gibbons, Sergey Rubanov 10 |11 | 18 | 19 | 20 | 21 |
`Promise.any()` accepts an iterable of promises and returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an `AggregateError` holding the rejection reasons if all of the given promises are rejected.
24 |Well-known intrinsics are built-in objects that are explicitly referenced by the algorithms of this specification and which usually have realm-specific identities. Unless otherwise specified each intrinsic object actually corresponds to a set of similar objects, one per realm.
29 |Within this specification a reference such as %name% means the intrinsic object, associated with the current realm, corresponding to the name. Determination of the current realm and its intrinsics is described in
35 | Intrinsic Name 36 | | 37 |38 | Global Name 39 | | 40 |41 | ECMAScript Language Association 42 | | 43 |
---|---|---|
46 | %AggregateError% 47 | | 48 |49 | `AggregateError` 50 | | 51 |
52 | The `AggregateError` constructor ( |
54 |
57 | %AggregateErrorPrototype% 58 | | 59 |60 | `AggregateError.prototype` 61 | | 62 |63 | The initial value of the `prototype` data property of %AggregateError%; i.e., %AggregateError.prototype% 64 | | 65 |
Instances of Error objects are thrown as exceptions when runtime errors occur. The Error objects may also serve as base objects for user-defined exception classes.
74 |When an ECMAScript implementation detects a runtime error, it throws a new instance of one of the _NativeError_ objects defined in
Error instances are ordinary objects that inherit properties from the Error prototype object and have an [[ErrorData]] internal slot whose value is *undefined*. The only specified uses of [[ErrorData]] is to identify Error, AggregateError, and _NativeError_ instances as Error objects within `Object.prototype.toString`.
79 |A new instance of one of the _NativeError_ objects below or of the AggregateError object is thrown when a runtime error is detected. All of these objects share the same structure, as described in
For each error object, references to _NativeError_ in the definition should be replaced with the appropriate error object name from
_NativeError_ instances are ordinary objects that inherit properties from their _NativeError_ prototype object and have an [[ErrorData]] internal slot whose value is *undefined*. The only specified use of [[ErrorData]] is by `Object.prototype.toString` (
The AggregateError constructor:
102 |When the *AggregateError* function is called with arguments _errors_ and _message_, the following steps are taken:
112 |The AggregateError constructor:
129 |The initial value of `AggregateError.prototype` is the intrinsic object %AggregateError.prototype%.
137 |This property has the attributes { [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *false* }.
138 |The AggregateError prototype object:
144 |The initial value of `AggregateError.prototype.constructor` is the intrinsic object %AggregateError%.
154 |This property has the attributes { [[Writable]]: *true*, [[Enumerable]]: *false*, [[Configurable]]: *true* }.
155 |The initial value of `AggregateError.prototype.message` is the empty String.
160 |This property has the attributes { [[Writable]]: *true*, [[Enumerable]]: *false*, [[Configurable]]: *true* }.
161 |The initial value of `AggregateError.prototype.name` is `"AggregateError"`.
166 |This property has the attributes { [[Writable]]: *true*, [[Enumerable]]: *false*, [[Configurable]]: *true* }.
167 |AggregateError instances are ordinary objects that inherit properties from their AggregateError prototype object and have an [[ErrorData]] internal slot whose value is *undefined*. The only specified use of [[ErrorData]] is by `Object.prototype.toString` (
The `any` function returns a promise that is fulfilled by the first given promise to be fulfilled, or rejected with an `AggregateError` holding the rejection reasons if all of the given promises are rejected. It resolves all elements of the passed iterable to promises as it runs this algorithm.
180 |The `any` function requires its *this* value to be a constructor function that supports the parameter conventions of the `Promise` constructor.
195 |When the PerformPromiseAny abstract operation is called with arguments _iteratorRecord_, _constructor_, _resultCapability_, and _promiseResolve_, the following steps are taken:
200 |A `Promise.any` reject element function is an anonymous built-in function that is used to reject a specific `Promise.any` element. Each `Promise.any` reject element function has [[Index]], [[Errors]], [[Capability]], [[RemainingElements]], and [[AlreadyCalled]] internal slots.
240 |When a `Promise.any` reject element function is called with argument _x_, the following steps are taken:
241 |The `"length"` property of a `Promise.any` reject element function is 1.
259 |The abstract operation IterableToList performs the following steps:
265 |