From 96cbcd6da4bf2aad72bee76f5272c0b83511cff0 Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Thu, 25 Jul 2019 23:13:05 +0900 Subject: [PATCH] feat(zone.js): support Promise.allSettled (#31849) PR Close #31849 --- packages/zone.js/lib/common/promise.ts | 45 ++++++++-- packages/zone.js/test/common/Promise.spec.ts | 94 ++++++++++++++++++++ 2 files changed, 131 insertions(+), 8 deletions(-) diff --git a/packages/zone.js/lib/common/promise.ts b/packages/zone.js/lib/common/promise.ts index 1faae6effc..4f6c849b0a 100644 --- a/packages/zone.js/lib/common/promise.ts +++ b/packages/zone.js/lib/common/promise.ts @@ -286,7 +286,20 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr return promise; } - static all(values: any): Promise { + static all(values: any): Promise { return ZoneAwarePromise.allWithCallback(values); } + + static allSettled(values: any): Promise { + const P = this && this.prototype instanceof ZoneAwarePromise ? this : ZoneAwarePromise; + return P.allWithCallback(values, { + thenCallback: (value: any) => ({status: 'fulfilled', value}), + errorCallback: (err: any) => ({status: 'rejected', reason: err}) + }); + } + + static allWithCallback(values: any, callback?: { + thenCallback: (value: any) => any, + errorCallback: (err: any) => any + }): Promise { let resolve: (v: any) => void; let reject: (v: any) => void; let promise = new this((res, rej) => { @@ -305,13 +318,29 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr } const curValueIndex = valueIndex; - value.then((value: any) => { - resolvedValues[curValueIndex] = value; - unresolvedCount--; - if (unresolvedCount === 0) { - resolve !(resolvedValues); - } - }, reject !); + try { + value.then( + (value: any) => { + resolvedValues[curValueIndex] = callback ? callback.thenCallback(value) : value; + unresolvedCount--; + if (unresolvedCount === 0) { + resolve !(resolvedValues); + } + }, + (err: any) => { + if (!callback) { + reject !(err); + } else { + resolvedValues[curValueIndex] = callback.errorCallback(err); + unresolvedCount--; + if (unresolvedCount === 0) { + resolve !(resolvedValues); + } + } + }); + } catch (thenErr) { + reject !(thenErr); + } unresolvedCount++; valueIndex++; diff --git a/packages/zone.js/test/common/Promise.spec.ts b/packages/zone.js/test/common/Promise.spec.ts index 7165b184be..5cf4170514 100644 --- a/packages/zone.js/test/common/Promise.spec.ts +++ b/packages/zone.js/test/common/Promise.spec.ts @@ -518,4 +518,98 @@ describe( testPromiseSubClass(done); } : function() { testPromiseSubClass(); }); }); + + describe('Promise.allSettled', () => { + const yes = function makeFulfilledResult(value: any) { + return {status: 'fulfilled', value: value}; + }; + const no = function makeRejectedResult(reason: any) { + return {status: 'rejected', reason: reason}; + }; + const a = {}; + const b = {}; + const c = {}; + const allSettled = (Promise as any).allSettled; + it('no promise values', (done: DoneFn) => { + allSettled([a, b, c]).then((results: any[]) => { + expect(results).toEqual([yes(a), yes(b), yes(c)]); + done(); + }); + }); + it('all fulfilled', (done: DoneFn) => { + allSettled([ + Promise.resolve(a), Promise.resolve(b), Promise.resolve(c) + ]).then((results: any[]) => { + expect(results).toEqual([yes(a), yes(b), yes(c)]); + done(); + }); + }); + it('all rejected', (done: DoneFn) => { + allSettled([ + Promise.reject(a), Promise.reject(b), Promise.reject(c) + ]).then((results: any[]) => { + expect(results).toEqual([no(a), no(b), no(c)]); + done(); + }); + }); + it('mixed', (done: DoneFn) => { + allSettled([a, Promise.resolve(b), Promise.reject(c)]).then((results: any[]) => { + expect(results).toEqual([yes(a), yes(b), no(c)]); + done(); + }); + }); + it('mixed should in zone', (done: DoneFn) => { + const zone = Zone.current.fork({name: 'settled'}); + const bPromise = Promise.resolve(b); + const cPromise = Promise.reject(c); + zone.run(() => { + allSettled([a, bPromise, cPromise]).then((results: any[]) => { + expect(results).toEqual([yes(a), yes(b), no(c)]); + expect(Zone.current.name).toEqual(zone.name); + done(); + }); + }); + }); + it('poisoned .then', (done: DoneFn) => { + const promise = new Promise(function() {}); + promise.then = function() { throw new EvalError(); }; + allSettled([promise]).then( + () => { fail('should not reach here'); }, + (reason: any) => { + expect(reason instanceof EvalError).toBe(true); + done(); + }); + }); + const Subclass = (function() { + try { + // eslint-disable-next-line no-new-func + return Function( + 'class Subclass extends Promise { constructor(...args) { super(...args); this.thenArgs = []; } then(...args) { Subclass.thenArgs.push(args); this.thenArgs.push(args); return super.then(...args); } } Subclass.thenArgs = []; return Subclass;')(); + } catch (e) { /**/ + } + + return false; + }()); + + describe('inheritance', () => { + it('preserves correct subclass', () => { + const promise = allSettled.call(Subclass, [1]); + expect(promise instanceof Subclass).toBe(true); + expect(promise.constructor).toEqual(Subclass); + }); + + it('invoke the subclass', () => { + Subclass.thenArgs.length = 0; + + const original = Subclass.resolve(); + expect(Subclass.thenArgs.length).toBe(0); + expect(original.thenArgs.length).toBe(0); + + allSettled.call(Subclass, [original]); + + expect(original.thenArgs.length).toBe(1); + expect(Subclass.thenArgs.length).toBe(1); + }); + }); + }); }));