Skip to content

Commit 65eb50e

Browse files
committed
feat(operator): add retryWhen operator. closes #129
1 parent 1f36d99 commit 65eb50e

File tree

5 files changed

+133
-1
lines changed

5 files changed

+133
-1
lines changed

spec/operators/retryWhen-spec.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* globals describe, it, expect */
2+
var Rx = require('../../dist/cjs/Rx');
3+
var Observable = Rx.Observable;
4+
5+
describe('Observable.prototype.retryWhen()', function () {
6+
it('should retry when notified via returned notifier on thrown error', function (done) {
7+
var retried = false;
8+
var expected = [1, 2, 1, 2];
9+
var i = 0;
10+
Observable.of(1, 2, 3)
11+
.map(function (n) {
12+
if (n === 3) {
13+
throw 'bad';
14+
}
15+
return n;
16+
})
17+
.retryWhen(function (errors) {
18+
return errors.map(function (x) {
19+
expect(x).toBe('bad');
20+
if (retried) {
21+
throw 'done';
22+
}
23+
retried = true;
24+
return x;
25+
});
26+
})
27+
.subscribe(function (x) {
28+
expect(x).toBe(expected[i++]);
29+
},
30+
function (err) {
31+
expect(err).toBe('done');
32+
done();
33+
})
34+
});
35+
36+
it('should retry when notified and complete on returned completion', function (done) {
37+
var expected = [1, 2, 1, 2];
38+
Observable.of(1, 2, 3)
39+
.map(function (n) {
40+
if (n === 3) {
41+
throw 'bad';
42+
}
43+
return n;
44+
})
45+
.retryWhen(function (errors) {
46+
return errors.take(1);
47+
})
48+
.subscribe(function (n) {
49+
expect(n).toBe(expected.shift());
50+
}, function (err) {
51+
throw 'error should not be called';
52+
}, function () {
53+
done();
54+
});
55+
});
56+
});

spec/subject-spec.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ describe('Subject', function () {
7070
});
7171

7272
it('should clean out unsubscribed subscribers', function (done) {
73-
debugger;
7473
var subject = new Subject();
7574

7675
var sub1 = subject.subscribe(function (x) {

src/Observable.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,4 +137,5 @@ export default class Observable<T> {
137137
multicast: (subjectFactory: () => Subject<T>) => ConnectableObservable<T>;
138138

139139
catch: (selector: (err: any, source: Observable<T>, caught: Observable<any>) => Observable<any>) => Observable<T>;
140+
retryWhen: (notifier: (errors: Observable<any>) => Observable<any>) => Observable<T>;
140141
}

src/Rx.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,10 @@ import partition from './operators/partition';
126126
observableProto.partition = partition;
127127

128128
import _catch from './operators/catch';
129+
import retryWhen from './operators/retryWhen';
129130

130131
observableProto.catch = _catch;
132+
observableProto.retryWhen = retryWhen;
131133

132134
export default {
133135
Subject,

src/operators/retryWhen.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import Operator from '../Operator';
2+
import Observer from '../Observer';
3+
import Subscriber from '../Subscriber';
4+
import Observable from '../Observable';
5+
import Subject from '../Subject';
6+
import Subscription from '../Subscription';
7+
8+
import tryCatch from '../util/tryCatch';
9+
import {errorObject} from '../util/errorObject';
10+
11+
export default function retryWhen<T>(notifier: (errors:Observable<any>) => Observable<any>) {
12+
return this.lift(new RetryWhenOperator(notifier, this));
13+
}
14+
15+
export class RetryWhenOperator<T, R> extends Operator<T, R> {
16+
constructor(protected notifier: (errors: Observable<any>) => Observable<any>, protected original:Observable<T>) {
17+
super();
18+
}
19+
20+
call(observer: Observer<T>): Observer<T> {
21+
return new RetryWhenSubscriber<T>(observer, this.notifier, this.original);
22+
}
23+
}
24+
25+
export class RetryWhenSubscriber<T> extends Subscriber<T> {
26+
errors: Subject<any>;
27+
retryNotifications: Observable<any>;
28+
retryNotificationSubscription: Subscription<any>;
29+
30+
constructor(destination: Observer<T>, public notifier: (errors: Observable<any>) => Observable<any>, public original: Observable<T>) {
31+
super(destination);
32+
}
33+
34+
_error(err: any) {
35+
if (!this.retryNotifications) {
36+
this.errors = new Subject();
37+
const notifications = tryCatch(this.notifier).call(this, this.errors);
38+
if (notifications === errorObject) {
39+
this.destination.error(errorObject.e);
40+
} else {
41+
this.retryNotifications = notifications;
42+
this.retryNotificationSubscription = notifications.subscribe(new RetryNotificationSubscriber(this));
43+
this.add(this.retryNotificationSubscription);
44+
}
45+
}
46+
this.errors.next(err);
47+
}
48+
49+
finalError(err: any) {
50+
this.destination.error(err);
51+
}
52+
53+
resubscribe() {
54+
this.original.subscribe(this);
55+
}
56+
}
57+
58+
export class RetryNotificationSubscriber<T> extends Subscriber<T> {
59+
constructor(public parent: RetryWhenSubscriber<any>) {
60+
super(null);
61+
}
62+
63+
_next(value: T) {
64+
this.parent.resubscribe();
65+
}
66+
67+
_error(err: any) {
68+
this.parent.finalError(err);
69+
}
70+
71+
_complete() {
72+
this.parent.complete();
73+
}
74+
}

0 commit comments

Comments
 (0)