From f7e26e9d982c61ad599a26b6e39e5aa27a24ea2d Mon Sep 17 00:00:00 2001 From: David <4661784+retyui@users.noreply.github.com> Date: Sat, 9 Oct 2021 14:57:40 +0300 Subject: [PATCH 1/2] Implement Promise.allSettled() - Docs: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled - TS: https://github.com/microsoft/TypeScript/blob/main/src/lib/es2020.promise.d.ts --- Readme.md | 13 ++ index.d.ts | 94 +++++++++++++- src/es6-extensions.js | 25 ++++ test/extensions-tests.js | 256 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 387 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index aa0b5f5..215abd9 100644 --- a/Readme.md +++ b/Readme.md @@ -150,6 +150,19 @@ Promise.all([Promise.resolve('a'), 'b', Promise.resolve('c')]) }) ``` +#### Promise.allSettled(array) + +Returns a promise that resolves after all of the given promises have either fulfilled or rejected, with an array of objects that each describes the outcome of each promise. + +```js +Promise.allSettled([Promise.resolve('a'), Promise.reject('error'), Promise.resolve('c')]) + .then(function (res) { + res[0] // { status: "fulfilled", value: 'a' } + res[1] // { status: "rejected", reason: 'error' } + res[2] // { status: "fulfilled", value: 'c' } + }) +``` + #### Promise.race(array) Returns a promise that resolves or rejects with the result of the first promise to resolve/reject, e.g. diff --git a/index.d.ts b/index.d.ts index e83d65f..f168a7b 100644 --- a/index.d.ts +++ b/index.d.ts @@ -34,6 +34,18 @@ interface ThenPromise extends Promise { nodeify(callback: (err: Error, value: T) => void): void; } +interface PromiseFulfilledResult { + status: "fulfilled"; + value: T; +} + +interface PromiseRejectedResult { + status: "rejected"; + reason: any; +} + +type PromiseSettledResult = PromiseFulfilledResult | PromiseRejectedResult; + interface ThenPromiseConstructor { /** * A reference to the prototype. @@ -48,6 +60,86 @@ interface ThenPromiseConstructor { */ new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => any): ThenPromise; + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike, T10 | PromiseLike]): ThenPromise<[PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult]>; + + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike, T9 | PromiseLike]): ThenPromise<[PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult]>; + + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike, T8 | PromiseLike]): ThenPromise<[PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult]>; + + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike, T7 | PromiseLike]): ThenPromise<[PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult]>; + + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike, T6 | PromiseLike]): ThenPromise<[PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult]>; + + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike , T5 | PromiseLike]): ThenPromise<[PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult]>; + + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike, T4 | PromiseLike ]): ThenPromise<[PromiseSettledResult, PromiseSettledResult, PromiseSettledResult, PromiseSettledResult]>; + + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: [T1 | PromiseLike, T2 | PromiseLike, T3 | PromiseLike]): ThenPromise<[PromiseSettledResult, PromiseSettledResult, PromiseSettledResult]>; + + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: [T1 | PromiseLike, T2 | PromiseLike]): ThenPromise<[PromiseSettledResult, PromiseSettledResult]>; + + /** + * Creates a Promise that is resolved with an array of results when all + * of the provided Promises resolve or reject. + * @param values An array of Promises. + * @returns A new Promise. + */ + allSettled(values: (T | PromiseLike)[]): ThenPromise[]>; + /** * Creates a ThenPromise that is resolved with an array of results when all of the provided Promises * resolve, or rejected when any ThenPromise is rejected. @@ -243,4 +335,4 @@ interface ThenPromiseConstructor { declare var ThenPromise: ThenPromiseConstructor; -export = ThenPromise; \ No newline at end of file +export = ThenPromise; diff --git a/src/es6-extensions.js b/src/es6-extensions.js index 1161b57..0924167 100644 --- a/src/es6-extensions.js +++ b/src/es6-extensions.js @@ -98,6 +98,31 @@ Promise.all = function (arr) { }); }; +Promise.allSettled = function (iterable) { + return Promise.all( + iterableToArray(iterable).map(function (item) { + function onFulfill(value) { + return { status: 'fulfilled', value: value }; + } + function onReject(reason) { + return { status: 'rejected', reason: reason }; + } + + if(item && (typeof item === 'object' || typeof item === 'function')){ + if(item instanceof Promise && item.then === Promise.prototype.then){ + return item.then(onFulfill, onReject); + } + var then = item.then; + if (typeof then === 'function') { + return new Promise(then.bind(item)).then(onFulfill, onReject) + } + } + + return onFulfill(item); + }) + ); +}; + Promise.reject = function (value) { return new Promise(function (resolve, reject) { reject(value); diff --git a/test/extensions-tests.js b/test/extensions-tests.js index b3ff636..337358c 100644 --- a/test/extensions-tests.js +++ b/test/extensions-tests.js @@ -130,6 +130,262 @@ describe('extensions', function () { }) }) }) + describe('Promise.allSettled(...)', function () { + describe('an array', function () { + describe('that is empty', function () { + it('returns a promise for an empty array', function (done) { + var res = Promise.allSettled([]) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res.length === 0) + }) + .nodeify(done) + }) + }) + describe('of objects', function () { + it('returns a promise for the array', function (done) { + var res = Promise.allSettled([a, b, c]) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "fulfilled") + assert(res[1].value === b) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + }) + .nodeify(done) + }) + }) + describe('of promises', function () { + it('returns a promise for an array containing the fulfilled values', function (done) { + var d = {} + var resolveD + var res = Promise.allSettled([A, B, C, new Promise(function (resolve) { resolveD = resolve })]) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "fulfilled") + assert(res[1].value === b) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + assert(res[3].status === "fulfilled") + assert(res[3].value === d) + }) + .nodeify(done) + resolveD(d) + }) + }) + describe('of mixed values', function () { + it('returns a promise for an array containing the fulfilled values', function (done) { + var res = Promise.allSettled([A, b, C]) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "fulfilled") + assert(res[1].value === b) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + }) + .nodeify(done) + }) + }) + describe('containing at least one rejected promise', function () { + it('should not rejects the resulting promise', function (done) { + var res = Promise.allSettled([A, rejected, C]) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "rejected") + assert(res[1].reason === rejection) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + }) + .nodeify(done) + }) + }) + describe('containing at least one eventually rejected promise', function () { + it('rejects the resulting promise', function (done) { + var rejectB + var rejected = new Promise(function (resolve, reject) { rejectB = reject }) + var res = Promise.allSettled([A, rejected, C]) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "rejected") + assert(res[1].reason === rejection) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + }) + .nodeify(done) + rejectB(rejection) + }) + }) + describe('with a promise that resolves twice', function () { + it('still waits for all the other promises', function (done) { + var a = 1; + var fakePromise = {then: function (onFulfilled) { onFulfilled(a); onFulfilled(2) }} + var eventuallyRejected = {then: function (_, onRejected) { this.onRejected = onRejected }} + var res = Promise.allSettled([fakePromise, eventuallyRejected]) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "rejected") + assert(res[1].reason === rejection) + }) + .nodeify(done) + eventuallyRejected.onRejected(rejection); + }) + }) + describe('when given a foreign promise', function () { + it('should provide the correct value of `this`', function (done) { + var p = {then: function (onFulfilled) { onFulfilled({self: this}); }}; + Promise.allSettled([p]).then(function (results) { + assert(p === results[0].value.self); + }).nodeify(done); + }); + }); + }) + describe('a Set', function () { + describe('that is empty', function () { + it('returns a promise for an empty array', function (done) { + var res = Promise.allSettled(new Set([])) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res.length === 0) + }) + .nodeify(done) + }) + }) + describe('of objects', function () { + it('returns a promise for the array', function (done) { + var res = Promise.allSettled(new Set([a, b, c])) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "fulfilled") + assert(res[1].value === b) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + }) + .nodeify(done) + }) + }) + describe('of promises', function () { + it('returns a promise for an array containing the fulfilled values', function (done) { + var d = {} + var resolveD + var res = Promise.allSettled(new Set([A, B, C, new Promise(function (resolve) { resolveD = resolve })])) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "fulfilled") + assert(res[1].value === b) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + assert(res[3].status === "fulfilled") + assert(res[3].value === d) + }) + .nodeify(done) + resolveD(d) + }) + }) + describe('of mixed values', function () { + it('returns a promise for an array containing the fulfilled values', function (done) { + var res = Promise.allSettled(new Set([A, b, C])) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "fulfilled") + assert(res[1].value === b) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + }) + .nodeify(done) + }) + }) + describe('containing at least one rejected promise', function () { + it('rejects the resulting promise', function (done) { + var res = Promise.allSettled(new Set([A, rejected, C])) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "rejected") + assert(res[1].reason === rejection) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + }) + .nodeify(done) + }) + }) + describe('containing at least one eventually rejected promise', function () { + it('rejects the resulting promise', function (done) { + var rejectB + var rejected = new Promise(function (resolve, reject) { rejectB = reject }) + var res = Promise.allSettled(new Set([A, rejected, C])) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "rejected") + assert(res[1].reason === rejection) + assert(res[2].status === "fulfilled") + assert(res[2].value === c) + }) + .nodeify(done) + rejectB(rejection) + }) + }) + describe('with a promise that resolves twice', function () { + it('still waits for all the other promises', function (done) { + var a = 1 + var fakePromise = {then: function (onFulfilled) { onFulfilled(a); onFulfilled(2) }} + var eventuallyRejected = {then: function (_, onRejected) { this.onRejected = onRejected }} + var res = Promise.allSettled(new Set([fakePromise, eventuallyRejected])) + assert(res instanceof Promise) + res.then(function (res) { + assert(Array.isArray(res)) + assert(res[0].status === "fulfilled") + assert(res[0].value === a) + assert(res[1].status === "rejected") + assert(res[1].reason === rejection) + }) + .nodeify(done) + eventuallyRejected.onRejected(rejection); + }) + }) + describe('when given a foreign promise', function () { + it('should provide the correct value of `this`', function (done) { + var p = {then: function (onFulfilled) { onFulfilled({self: this}); }}; + Promise.allSettled(new Set([p])).then(function (results) { + assert(p === results[0].value.self); + }).nodeify(done); + }); + }); + }) + }) describe('Promise.all(...)', function () { describe('an array', function () { describe('that is empty', function () { From 8c4fd0c577432e48743b0ea17c342512f763332c Mon Sep 17 00:00:00 2001 From: Forbes Lindesay Date: Wed, 31 Aug 2022 12:49:24 +0100 Subject: [PATCH 2/2] perf: extract closures --- src/es6-extensions.js | 42 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/es6-extensions.js b/src/es6-extensions.js index 0924167..3f48e4a 100644 --- a/src/es6-extensions.js +++ b/src/es6-extensions.js @@ -98,29 +98,27 @@ Promise.all = function (arr) { }); }; -Promise.allSettled = function (iterable) { - return Promise.all( - iterableToArray(iterable).map(function (item) { - function onFulfill(value) { - return { status: 'fulfilled', value: value }; - } - function onReject(reason) { - return { status: 'rejected', reason: reason }; - } - - if(item && (typeof item === 'object' || typeof item === 'function')){ - if(item instanceof Promise && item.then === Promise.prototype.then){ - return item.then(onFulfill, onReject); - } - var then = item.then; - if (typeof then === 'function') { - return new Promise(then.bind(item)).then(onFulfill, onReject) - } - } +function onSettledFulfill(value) { + return { status: 'fulfilled', value: value }; +} +function onSettledReject(reason) { + return { status: 'rejected', reason: reason }; +} +function mapAllSettled(item) { + if(item && (typeof item === 'object' || typeof item === 'function')){ + if(item instanceof Promise && item.then === Promise.prototype.then){ + return item.then(onSettledFulfill, onSettledReject); + } + var then = item.then; + if (typeof then === 'function') { + return new Promise(then.bind(item)).then(onSettledFulfill, onSettledReject) + } + } - return onFulfill(item); - }) - ); + return onSettledFulfill(item); +} +Promise.allSettled = function (iterable) { + return Promise.all(iterableToArray(iterable).map(mapAllSettled)); }; Promise.reject = function (value) {