Skip to content

Commit 94b4c01

Browse files
committed
feat(catch): add catch operator, related to #141, closes #130
1 parent e5cf568 commit 94b4c01

File tree

4 files changed

+164
-0
lines changed

4 files changed

+164
-0
lines changed

spec/operators/catch-spec.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/* globals describe, it, expect */
2+
var Rx = require('../../dist/cjs/Rx');
3+
var Observable = Rx.Observable;
4+
5+
describe('Observable.prototype.catch()', function () {
6+
it('should pass the error as the first argument', function (done) {
7+
Observable.throw('bad')
8+
.catch(function (err) {
9+
expect(err).toBe('bad');
10+
return Observable.empty();
11+
})
12+
.subscribe(function () { },
13+
function (err) {
14+
expect('this was called').not.toBeTruthy();
15+
},
16+
done);
17+
});
18+
19+
it('should catch the error and allow the return of a new observable to use', function (done) {
20+
var expected = [1, 2, 'foo'];
21+
Observable.of(1, 2, 3)
22+
.map(function (n) {
23+
if (n === 3) {
24+
throw 'bad';
25+
}
26+
return n;
27+
})
28+
.catch(function (err) {
29+
return Observable.of('foo');
30+
})
31+
.subscribe(function (x) {
32+
expect(x).toBe(expected.shift());
33+
}, function (err) {
34+
expect('this was called').not.toBeTruthy();
35+
}, function () {
36+
done();
37+
});
38+
});
39+
40+
it('should catch and allow the observable to be repeated with the third (caught) argument', function (done) {
41+
var expected = [1, 2, 1, 2, 1, 2];
42+
var retries = 0;
43+
Observable.of(1, 2, 3)
44+
.map(function (n) {
45+
if (n === 3) {
46+
throw 'bad';
47+
}
48+
return n;
49+
})
50+
.catch(function (err, caught) {
51+
if (retries++ == 2) {
52+
throw 'done';
53+
}
54+
return caught;
55+
})
56+
.subscribe(function (x) {
57+
expect(x).toBe(expected.shift());
58+
}, function (err) {
59+
expect(err).toBe('done');
60+
done();
61+
}, function () {
62+
expect('this was called').not.toBeTruthy();
63+
})
64+
});
65+
66+
it('should complete if you return Observable.empty()', function (done) {
67+
var expected = [1, 2];
68+
Observable.of(1, 2, 3)
69+
.map(function (n) {
70+
if (n === 3) {
71+
throw 'bad';
72+
}
73+
return n;
74+
})
75+
.catch(function (err) {
76+
return Observable.empty();
77+
})
78+
.subscribe(function (x) {
79+
expect(x).toBe(expected.shift());
80+
}, function (err) {
81+
expect('this was called').not.toBeTruthy();
82+
}, function () {
83+
done();
84+
});
85+
});
86+
87+
88+
it('should error if you return Observable.throw()', function (done) {
89+
var expected = [1, 2];
90+
Observable.of(1, 2, 3)
91+
.map(function (n) {
92+
if (n === 3) {
93+
throw 'bad';
94+
}
95+
return n;
96+
})
97+
.catch(function (err) {
98+
return Observable.throw('haha');
99+
})
100+
.subscribe(function (x) {
101+
expect(x).toBe(expected.shift());
102+
}, function (err) {
103+
expect(err).toBe('haha');
104+
done();
105+
}, function () {
106+
expect('this was called').not.toBeTruthy();
107+
});
108+
});
109+
});

src/Observable.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,6 @@ export default class Observable<T> {
134134

135135
publish: () => ConnectableObservable<T>;
136136
multicast: (subjectFactory: () => Subject<T>) => ConnectableObservable<T>;
137+
138+
catch: (selector: (err: any, source: Observable<T>, caught: Observable<any>) => Observable<any>) => Observable<T>;
137139
}

src/Rx.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ import partition from './operators/partition';
119119

120120
observableProto.partition = partition;
121121

122+
import _catch from './operators/catch';
123+
124+
observableProto.catch = _catch;
125+
122126
export default {
123127
Subject,
124128
Scheduler,

src/operators/catch.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import Operator from '../Operator';
2+
import Observer from '../Observer';
3+
import Subscriber from '../Subscriber';
4+
import Observable from '../Observable';
5+
6+
import tryCatch from '../util/tryCatch';
7+
import {errorObject} from '../util/errorObject';
8+
9+
export default function _catch<T>(selector: (err:any, caught:Observable<any>) => Observable<any>) {
10+
var catchOperator = new CatchOperator(selector);
11+
var caught = this.lift(catchOperator);
12+
catchOperator.caught = caught;
13+
return caught;
14+
}
15+
16+
export class CatchOperator<T, R> extends Operator<T, R> {
17+
selector: (err:any, caught:Observable<any>) => Observable<any>;
18+
caught: Observable<any>;
19+
source: Observable<T>;
20+
21+
constructor(selector: (err:any, caught:Observable<any>) => Observable<any>) {
22+
super();
23+
this.selector = selector;
24+
}
25+
26+
call(observer: Observer<T>): Observer<T> {
27+
return new CatchSubscriber(observer, this.selector, this.caught);
28+
}
29+
}
30+
31+
export class CatchSubscriber<T> extends Subscriber<T> {
32+
selector: (err:any, caught:Observable<any>) => Observable<any>;
33+
caught: Observable<any>;
34+
35+
constructor(destination: Observer<T>, selector: (err:any, caught:Observable<any>) => Observable<any>, caught: Observable<any>) {
36+
super(destination);
37+
this.selector = selector;
38+
this.caught = caught;
39+
}
40+
41+
_error(err) {
42+
const result = tryCatch(this.selector)(err, this.caught);
43+
if (result === errorObject) {
44+
this.destination.error(errorObject.e);
45+
} else {
46+
this.add(result.subscribe(this.destination));
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)