From a26dce484669a2d5dc2d01828df95d802833f245 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Sun, 13 Feb 2022 08:53:31 +0900 Subject: [PATCH] feat(zone.js): add Promise.any() implementation Implements `Promise.any()` introduced in ES2021. Close #44393 --- goldens/size-tracking/aio-payloads.json | 2 +- .../size-tracking/integration-payloads.json | 16 +-- packages/zone.js/lib/common/promise.ts | 46 ++++++- packages/zone.js/test/common/Promise.spec.ts | 115 ++++++++++++++++++ packages/zone.js/tsconfig.json | 4 +- 5 files changed, 171 insertions(+), 12 deletions(-) diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index e275c7c88f9f..53a33f37e355 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -16,7 +16,7 @@ "uncompressed": { "runtime": 4343, "main": 450179, - "polyfills": 37297, + "polyfills": 37823, "styles": 70416, "light-theme": 77582, "dark-theme": 77711 diff --git a/goldens/size-tracking/integration-payloads.json b/goldens/size-tracking/integration-payloads.json index 78a4568db4e7..c950f6df56f4 100644 --- a/goldens/size-tracking/integration-payloads.json +++ b/goldens/size-tracking/integration-payloads.json @@ -4,7 +4,7 @@ "uncompressed": { "runtime": 1083, "main": 126218, - "polyfills": 37226 + "polyfills": 37778 } } }, @@ -16,7 +16,7 @@ "main": "Likely there is a missing PURE annotation https://github.com/angular/angular/pull/43344", "main": "Tracking issue: https://github.com/angular/angular/issues/43568", "main": 20378, - "polyfills": 37250 + "polyfills": 37802 } } }, @@ -25,7 +25,7 @@ "uncompressed": { "runtime": 1105, "main": 131882, - "polyfills": 37248 + "polyfills": 37800 } } }, @@ -34,7 +34,7 @@ "uncompressed": { "runtime": 929, "main": 124544, - "polyfills": 37933 + "polyfills": 38488 } } }, @@ -43,7 +43,7 @@ "uncompressed": { "runtime": 2835, "main": 233348, - "polyfills": 37244, + "polyfills": 37796, "src_app_lazy_lazy_module_ts": 795 } } @@ -53,7 +53,7 @@ "uncompressed": { "runtime": 1063, "main": 158556, - "polyfills": 36975 + "polyfills": 37758 } } }, @@ -62,7 +62,7 @@ "uncompressed": { "runtime": 1070, "main": 158300, - "polyfills": 37242 + "polyfills": 37768 } } }, @@ -77,4 +77,4 @@ } } } -} \ No newline at end of file +} diff --git a/packages/zone.js/lib/common/promise.ts b/packages/zone.js/lib/common/promise.ts index 9103709ac2df..efbd15fe5900 100644 --- a/packages/zone.js/lib/common/promise.ts +++ b/packages/zone.js/lib/common/promise.ts @@ -254,7 +254,8 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr const promiseState = (promise as any)[symbolState]; const delegate = promiseState ? (typeof onFulfilled === 'function') ? onFulfilled : forwardResolution : - (typeof onRejected === 'function') ? onRejected : forwardRejection; + (typeof onRejected === 'function') ? onRejected : + forwardRejection; zone.scheduleMicroTask(source, () => { try { const parentPromiseValue = (promise as any)[symbolValue]; @@ -283,6 +284,8 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr const noop = function() {}; + const AggregateError = global.AggregateError; + class ZoneAwarePromise implements Promise { static toString() { return ZONE_AWARE_PROMISE_TO_STRING; @@ -296,6 +299,47 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr return resolvePromise(>new this(null as any), REJECTED, error); } + static any(values: Iterable>): Promise { + if (!values || typeof values[Symbol.iterator] !== 'function') { + return Promise.reject(new AggregateError([], 'All promises were rejected')); + } + const promises: Promise>[] = []; + let count = 0; + try { + for (let v of values) { + count++; + promises.push(ZoneAwarePromise.resolve(v)); + } + } catch (err) { + return Promise.reject(new AggregateError([], 'All promises were rejected')); + } + if (count === 0) { + return Promise.reject(new AggregateError([], 'All promises were rejected')); + } + let finished = false; + const errors: any[] = []; + return new ZoneAwarePromise((resolve, reject) => { + for (let i = 0; i < promises.length; i++) { + promises[i].then( + v => { + if (finished) { + return; + } + finished = true; + resolve(v); + }, + err => { + errors.push(err); + count--; + if (count === 0) { + finished = true; + reject(new AggregateError(errors, 'All promises were rejected')); + } + }); + } + }); + }; + static race(values: PromiseLike[]): Promise { let resolve: (v: any) => void; let reject: (v: any) => void; diff --git a/packages/zone.js/test/common/Promise.spec.ts b/packages/zone.js/test/common/Promise.spec.ts index e2f9bbdc094f..f2ed7cc4d0b3 100644 --- a/packages/zone.js/test/common/Promise.spec.ts +++ b/packages/zone.js/test/common/Promise.spec.ts @@ -599,6 +599,121 @@ describe( }); }); + describe('Promise.any', () => { + const any = (Promise as any).any; + it('undefined parameters', (done: DoneFn) => { + any().then( + () => { + fail('should not get a resolved promise.'); + }, + (err: any) => { + expect(err.message).toEqual('All promises were rejected'); + expect(err.errors).toEqual([]); + done(); + }); + }); + it('invalid iterable', (done: DoneFn) => { + const invalidIterable: any = {}; + invalidIterable[Symbol.iterator] = () => 2; + any(invalidIterable) + .then( + () => { + fail('should not get a resolved promise.'); + }, + (err: any) => { + expect(err.message).toEqual('All promises were rejected'); + expect(err.errors).toEqual([]); + done(); + }); + }); + it('empty parameters', (done: DoneFn) => { + any([]).then( + () => { + fail('should not get a resolved promise.'); + }, + (err: any) => { + expect(err.message).toEqual('All promises were rejected'); + expect(err.errors).toEqual([]); + done(); + }); + }); + it('non promises parameters', (done: DoneFn) => { + any([1, 'test']) + .then( + (v: any) => { + expect(v).toBe(1); + done(); + }, + (err: any) => { + fail('should not get a rejected promise.'); + }); + }); + it('mixed parameters, non promise first', (done: DoneFn) => { + any([1, Promise.resolve(2)]) + .then( + (v: any) => { + expect(v).toBe(1); + done(); + }, + (err: any) => { + fail('should not get a rejected promise.'); + }); + }); + it('mixed parameters, promise first', (done: DoneFn) => { + any([Promise.resolve(1), 2]) + .then( + (v: any) => { + expect(v).toBe(1); + done(); + }, + (err: any) => { + fail('should not get a rejected promise.'); + }); + }); + it('all ok promises', (done: DoneFn) => { + any([Promise.resolve(1), Promise.resolve(2)]) + .then( + (v: any) => { + expect(v).toBe(1); + done(); + }, + (err: any) => { + fail('should not get a rejected promise.'); + }); + }); + it('all promises, first rejected', (done: DoneFn) => { + any([Promise.reject('error'), Promise.resolve(2)]) + .then( + (v: any) => { + expect(v).toBe(2); + done(); + }, + (err: any) => { + fail('should not get a rejected promise.'); + }); + }); + it('all promises, second rejected', (done: DoneFn) => { + any([Promise.resolve(1), Promise.reject('error')]) + .then( + (v: any) => { + expect(v).toBe(1); + done(); + }, + (err: any) => { + fail('should not get a rejected promise.'); + }); + }); + it('all rejected promises', (done: DoneFn) => { + any([ + Promise.reject('error1'), Promise.reject('error2') + ]).then((v: any) => {fail('should not get a resolved promise.')}, (err: any) => { + expect(err.message).toEqual('All promises were rejected'); + expect(err.errors).toEqual(['error1', 'error2']); + done(); + }); + }); + }); + describe('Promise.allSettled', () => { const yes = function makeFulfilledResult(value: any) { return {status: 'fulfilled', value: value}; diff --git a/packages/zone.js/tsconfig.json b/packages/zone.js/tsconfig.json index 5ce77bf9fac3..ba2007a01913 100644 --- a/packages/zone.js/tsconfig.json +++ b/packages/zone.js/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es5", + "target": "es2015", "outDir": "build", "inlineSourceMap": true, "inlineSources": true, @@ -38,4 +38,4 @@ "test/node_error_entry_point.ts", "test/node_tests.ts" ] -} \ No newline at end of file +}