Skip to content

Commit 96cbcd6

Browse files
JiaLiPassionalxhub
authored andcommitted
feat(zone.js): support Promise.allSettled (#31849)
PR Close #31849
1 parent 2a6e6c0 commit 96cbcd6

2 files changed

Lines changed: 131 additions & 8 deletions

File tree

packages/zone.js/lib/common/promise.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,20 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
286286
return promise;
287287
}
288288

289-
static all<R>(values: any): Promise<R> {
289+
static all<R>(values: any): Promise<R> { return ZoneAwarePromise.allWithCallback(values); }
290+
291+
static allSettled<R>(values: any): Promise<R> {
292+
const P = this && this.prototype instanceof ZoneAwarePromise ? this : ZoneAwarePromise;
293+
return P.allWithCallback(values, {
294+
thenCallback: (value: any) => ({status: 'fulfilled', value}),
295+
errorCallback: (err: any) => ({status: 'rejected', reason: err})
296+
});
297+
}
298+
299+
static allWithCallback<R>(values: any, callback?: {
300+
thenCallback: (value: any) => any,
301+
errorCallback: (err: any) => any
302+
}): Promise<R> {
290303
let resolve: (v: any) => void;
291304
let reject: (v: any) => void;
292305
let promise = new this<R>((res, rej) => {
@@ -305,13 +318,29 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
305318
}
306319

307320
const curValueIndex = valueIndex;
308-
value.then((value: any) => {
309-
resolvedValues[curValueIndex] = value;
310-
unresolvedCount--;
311-
if (unresolvedCount === 0) {
312-
resolve !(resolvedValues);
313-
}
314-
}, reject !);
321+
try {
322+
value.then(
323+
(value: any) => {
324+
resolvedValues[curValueIndex] = callback ? callback.thenCallback(value) : value;
325+
unresolvedCount--;
326+
if (unresolvedCount === 0) {
327+
resolve !(resolvedValues);
328+
}
329+
},
330+
(err: any) => {
331+
if (!callback) {
332+
reject !(err);
333+
} else {
334+
resolvedValues[curValueIndex] = callback.errorCallback(err);
335+
unresolvedCount--;
336+
if (unresolvedCount === 0) {
337+
resolve !(resolvedValues);
338+
}
339+
}
340+
});
341+
} catch (thenErr) {
342+
reject !(thenErr);
343+
}
315344

316345
unresolvedCount++;
317346
valueIndex++;

packages/zone.js/test/common/Promise.spec.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,4 +518,98 @@ describe(
518518
testPromiseSubClass(done);
519519
} : function() { testPromiseSubClass(); });
520520
});
521+
522+
describe('Promise.allSettled', () => {
523+
const yes = function makeFulfilledResult(value: any) {
524+
return {status: 'fulfilled', value: value};
525+
};
526+
const no = function makeRejectedResult(reason: any) {
527+
return {status: 'rejected', reason: reason};
528+
};
529+
const a = {};
530+
const b = {};
531+
const c = {};
532+
const allSettled = (Promise as any).allSettled;
533+
it('no promise values', (done: DoneFn) => {
534+
allSettled([a, b, c]).then((results: any[]) => {
535+
expect(results).toEqual([yes(a), yes(b), yes(c)]);
536+
done();
537+
});
538+
});
539+
it('all fulfilled', (done: DoneFn) => {
540+
allSettled([
541+
Promise.resolve(a), Promise.resolve(b), Promise.resolve(c)
542+
]).then((results: any[]) => {
543+
expect(results).toEqual([yes(a), yes(b), yes(c)]);
544+
done();
545+
});
546+
});
547+
it('all rejected', (done: DoneFn) => {
548+
allSettled([
549+
Promise.reject(a), Promise.reject(b), Promise.reject(c)
550+
]).then((results: any[]) => {
551+
expect(results).toEqual([no(a), no(b), no(c)]);
552+
done();
553+
});
554+
});
555+
it('mixed', (done: DoneFn) => {
556+
allSettled([a, Promise.resolve(b), Promise.reject(c)]).then((results: any[]) => {
557+
expect(results).toEqual([yes(a), yes(b), no(c)]);
558+
done();
559+
});
560+
});
561+
it('mixed should in zone', (done: DoneFn) => {
562+
const zone = Zone.current.fork({name: 'settled'});
563+
const bPromise = Promise.resolve(b);
564+
const cPromise = Promise.reject(c);
565+
zone.run(() => {
566+
allSettled([a, bPromise, cPromise]).then((results: any[]) => {
567+
expect(results).toEqual([yes(a), yes(b), no(c)]);
568+
expect(Zone.current.name).toEqual(zone.name);
569+
done();
570+
});
571+
});
572+
});
573+
it('poisoned .then', (done: DoneFn) => {
574+
const promise = new Promise(function() {});
575+
promise.then = function() { throw new EvalError(); };
576+
allSettled([promise]).then(
577+
() => { fail('should not reach here'); },
578+
(reason: any) => {
579+
expect(reason instanceof EvalError).toBe(true);
580+
done();
581+
});
582+
});
583+
const Subclass = (function() {
584+
try {
585+
// eslint-disable-next-line no-new-func
586+
return Function(
587+
'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;')();
588+
} catch (e) { /**/
589+
}
590+
591+
return false;
592+
}());
593+
594+
describe('inheritance', () => {
595+
it('preserves correct subclass', () => {
596+
const promise = allSettled.call(Subclass, [1]);
597+
expect(promise instanceof Subclass).toBe(true);
598+
expect(promise.constructor).toEqual(Subclass);
599+
});
600+
601+
it('invoke the subclass', () => {
602+
Subclass.thenArgs.length = 0;
603+
604+
const original = Subclass.resolve();
605+
expect(Subclass.thenArgs.length).toBe(0);
606+
expect(original.thenArgs.length).toBe(0);
607+
608+
allSettled.call(Subclass, [original]);
609+
610+
expect(original.thenArgs.length).toBe(1);
611+
expect(Subclass.thenArgs.length).toBe(1);
612+
});
613+
});
614+
});
521615
}));

0 commit comments

Comments
 (0)