feat(zone.js): support Promise.allSettled (#31849)

PR Close #31849
This commit is contained in:
JiaLiPassion 2019-07-25 23:13:05 +09:00 committed by Alex Rickabaugh
parent 2a6e6c02ed
commit 96cbcd6da4
2 changed files with 131 additions and 8 deletions

View File

@ -286,7 +286,20 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
return promise;
}
static all<R>(values: any): Promise<R> {
static all<R>(values: any): Promise<R> { return ZoneAwarePromise.allWithCallback(values); }
static allSettled<R>(values: any): Promise<R> {
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<R>(values: any, callback?: {
thenCallback: (value: any) => any,
errorCallback: (err: any) => any
}): Promise<R> {
let resolve: (v: any) => void;
let reject: (v: any) => void;
let promise = new this<R>((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++;

View File

@ -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);
});
});
});
}));