Skip to content

Commit 96aa14d

Browse files
JiaLiPassionmhevery
authored andcommitted
fix(zone.js): zone patch rxjs should return null _unsubscribe correctly. (#37091)
Close #31684. In some rxjs operator, such as `retryWhen`, rxjs internally will set `Subscription._unsubscribe` method to null, and the current zone.js monkey patch didn't handle this case correctly, even rxjs set _unsubscribe to null, zone.js still return a function by finding the prototype chain. This PR fix this issue and the following test will pass. ``` const errorGenerator = () => { return throwError(new Error('error emit')); }; const genericRetryStrategy = (finalizer: () => void) => (attempts: Observable<any>) => attempts.pipe( mergeMap((error, i) => { const retryAttempt = i + 1; if (retryAttempt > 3) { return throwError(error); } return timer(retryAttempt * 1); }), finalize(() => finalizer())); errorGenerator() .pipe( retryWhen(genericRetryStrategy(() => { expect(log.length).toBe(3); done(); })), catchError(error => of(error))) .subscribe() ``` PR Close #37091
1 parent 2b6ab57 commit 96aa14d

3 files changed

Lines changed: 49 additions & 1 deletion

File tree

packages/zone.js/lib/rxjs/rxjs.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ type ZoneSubscriberContext = {
115115
_zoneUnsubscribe: {value: null, writable: true, configurable: true},
116116
_unsubscribe: {
117117
get: function(this: Subscription) {
118-
if ((this as any)._zoneUnsubscribe) {
118+
if ((this as any)._zoneUnsubscribe || (this as any)._zoneUnsubscribeCleared) {
119119
return (this as any)._zoneUnsubscribe;
120120
}
121121
const proto = Object.getPrototypeOf(this);
@@ -125,7 +125,13 @@ type ZoneSubscriberContext = {
125125
(this as any)._zone = Zone.current;
126126
if (!unsubscribe) {
127127
(this as any)._zoneUnsubscribe = unsubscribe;
128+
// In some operator such as `retryWhen`, the _unsubscribe
129+
// method will be set to null, so we need to set another flag
130+
// to tell that we should return null instead of finding
131+
// in the prototype chain.
132+
(this as any)._zoneUnsubscribeCleared = true;
128133
} else {
134+
(this as any)._zoneUnsubscribeCleared = false;
129135
(this as any)._zoneUnsubscribe = function() {
130136
if (this._zone && this._zone !== Zone.current) {
131137
return this._zone.run(unsubscribe, this, arguments);
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
import {Observable, of, throwError, timer} from 'rxjs';
9+
import {catchError, finalize, mergeMap, retryWhen} from 'rxjs/operators';
10+
11+
describe('retryWhen', () => {
12+
let log: any[];
13+
const genericRetryStrategy = (finalizer: () => void) => (attempts: Observable<any>) =>
14+
attempts.pipe(
15+
mergeMap((error, i) => {
16+
const retryAttempt = i + 1;
17+
if (retryAttempt > 3) {
18+
return throwError(error);
19+
}
20+
log.push(error);
21+
return timer(retryAttempt * 1);
22+
}),
23+
finalize(() => finalizer()));
24+
25+
const errorGenerator = () => {
26+
return throwError(new Error('error emit'));
27+
};
28+
beforeEach(() => {
29+
log = [];
30+
});
31+
32+
it('should retry max 3 times',
33+
(done: DoneFn) => {errorGenerator()
34+
.pipe(
35+
retryWhen(genericRetryStrategy(() => {
36+
expect(log.length).toBe(3);
37+
done();
38+
})),
39+
catchError(error => of(error)))
40+
.subscribe()});
41+
});

packages/zone.js/test/rxjs/rxjs.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import './rxjs.merge.spec';
2727
import './rxjs.never.spec';
2828
import './rxjs.of.spec';
2929
import './rxjs.range.spec';
30+
import './rxjs.retry.spec';
3031
import './rxjs.throw.spec';
3132
import './rxjs.timer.spec';
3233
import './rxjs.zip.spec';

0 commit comments

Comments
 (0)