Skip to content

Commit 539d8f0

Browse files
JiaLiPassionmhevery
authored andcommitted
fix: implement Symbol.specics of Promise (#34162)
Close #34105, #33989 PR Close #34162
1 parent 6b1a471 commit 539d8f0

2 files changed

Lines changed: 26 additions & 6 deletions

File tree

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,8 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
256256

257257
const ZONE_AWARE_PROMISE_TO_STRING = 'function ZoneAwarePromise() { [native code] }';
258258

259+
const noop = function() {};
260+
259261
class ZoneAwarePromise<R> implements Promise<R> {
260262
static toString() { return ZONE_AWARE_PROMISE_TO_STRING; }
261263

@@ -374,12 +376,17 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
374376

375377
get[Symbol.toStringTag]() { return 'Promise' as any; }
376378

379+
get[Symbol.species]() { return ZoneAwarePromise; }
380+
377381
then<TResult1 = R, TResult2 = never>(
378382
onFulfilled?: ((value: R) => TResult1 | PromiseLike<TResult1>)|undefined|null,
379383
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>)|undefined|
380384
null): Promise<TResult1|TResult2> {
381-
const chainPromise: Promise<TResult1|TResult2> =
382-
new (this.constructor as typeof ZoneAwarePromise)(null as any);
385+
let C = (this.constructor as any)[Symbol.species];
386+
if (!C || typeof C !== 'function') {
387+
C = ZoneAwarePromise;
388+
}
389+
const chainPromise: Promise<TResult1|TResult2> = new (C as typeof ZoneAwarePromise)(noop);
383390
const zone = Zone.current;
384391
if ((this as any)[symbolState] == UNRESOLVED) {
385392
(<any[]>(this as any)[symbolValue]).push(zone, chainPromise, onFulfilled, onRejected);
@@ -395,8 +402,11 @@ Zone.__load_patch('ZoneAwarePromise', (global: any, Zone: ZoneType, api: _ZonePr
395402
}
396403

397404
finally<U>(onFinally?: () => U | PromiseLike<U>): Promise<R> {
398-
const chainPromise: Promise<R|never> =
399-
new (this.constructor as typeof ZoneAwarePromise)(null as any);
405+
let C = (this.constructor as any)[Symbol.species];
406+
if (!C || typeof C !== 'function') {
407+
C = ZoneAwarePromise;
408+
}
409+
const chainPromise: Promise<R|never> = new (C as typeof ZoneAwarePromise)(noop);
400410
(chainPromise as any)[symbolFinally] = symbolFinally;
401411
const zone = Zone.current;
402412
if ((this as any)[symbolState] == UNRESOLVED) {

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,21 @@ describe(
103103
}).toThrowError('Must be an instanceof Promise.');
104104
});
105105

106-
xit('should allow subclassing', () => {
106+
it('should allow subclassing with Promise.specices', () => {
107107
class MyPromise extends Promise<any> {
108108
constructor(fn: any) { super(fn); }
109+
110+
static get[Symbol.species]() { return MyPromise; }
109111
}
110-
expect(new MyPromise(null).then(() => null) instanceof MyPromise).toBe(true);
112+
expect(new MyPromise(() => {}).then(() => null) instanceof MyPromise).toBe(true);
113+
});
114+
115+
it('Promise.specices should return ZoneAwarePromise', () => {
116+
const empty = function() {};
117+
const promise = Promise.resolve(1);
118+
const FakePromise = ((promise.constructor = {} as any) as any)[Symbol.species] = function(
119+
exec: any) { exec(empty, empty); };
120+
expect(promise.then(empty) instanceof FakePromise).toBe(true);
111121
});
112122

113123
it('should intercept scheduling of resolution and then', (done) => {

0 commit comments

Comments
 (0)