Skip to content

Commit 274c233

Browse files
Andre Medeirosbenlesh
authored andcommitted
feat(operator): add first operator
1 parent 8fe9e39 commit 274c233

File tree

4 files changed

+233
-0
lines changed

4 files changed

+233
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
var RxOld = require("rx");
2+
var RxNew = require("../../../../index");
3+
4+
module.exports = function (suite) {
5+
var predicate = function(value, index) {
6+
return value === 20;
7+
};
8+
9+
var oldFirstNoArgs = RxOld.Observable.range(0, 50, RxOld.Scheduler.immediate).first(predicate);
10+
var newFirstNoArgs = RxNew.Observable.range(0, 50).first(predicate);
11+
12+
return suite
13+
.add('old first(predicate) with immediate scheduler', function () {
14+
oldFirstNoArgs.subscribe(_next, _error, _complete);
15+
})
16+
.add('new first(predicate) with immediate scheduler', function () {
17+
newFirstNoArgs.subscribe(_next, _error, _complete);
18+
});
19+
20+
function _next(x) { }
21+
function _error(e) { }
22+
function _complete() { }
23+
};
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
var RxOld = require("rx");
2+
var RxNew = require("../../../../index");
3+
4+
module.exports = function (suite) {
5+
var oldFirstNoArgs = RxOld.Observable.range(0, 50, RxOld.Scheduler.immediate).first();
6+
var newFirstNoArgs = RxNew.Observable.range(0, 50).first();
7+
8+
return suite
9+
.add('old first() with immediate scheduler', function () {
10+
oldFirstNoArgs.subscribe(_next, _error, _complete);
11+
})
12+
.add('new first() with immediate scheduler', function () {
13+
newFirstNoArgs.subscribe(_next, _error, _complete);
14+
});
15+
16+
function _next(x) { }
17+
function _error(e) { }
18+
function _complete() { }
19+
};

spec/operators/first-spec.js

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/* globals describe, it, expect, expectObservable, hot, cold */
2+
var Rx = require('../../dist/cjs/Rx');
3+
4+
describe('Observable.prototype.first()', function() {
5+
it('should take the first value of an observable with one value', function() {
6+
var e1 = hot('---(a|)');
7+
var expected = '---(a|)';
8+
expectObservable(e1.first()).toBe(expected)
9+
});
10+
11+
it('should take the first value of an observable with many values', function() {
12+
var e1 = hot('--a--^--b----c---d--|');
13+
var expected = '---(b|)';
14+
expectObservable(e1.first()).toBe(expected)
15+
});
16+
17+
it('should error on empty', function() {
18+
var e1 = hot('--a--^----|');
19+
var expected = '-----#';
20+
expectObservable(e1.first()).toBe(expected, null, new Rx.EmptyError());
21+
});
22+
23+
it('should return the default value if source observable was empty', function() {
24+
var e1 = hot('-----^----|');
25+
var expected = '-----(a|)';
26+
expectObservable(e1.first(null, null, 'a')).toBe(expected);
27+
});
28+
29+
it('should propagate error from the source observable', function() {
30+
var e1 = hot('---^---#');
31+
var expected = '----#';
32+
expectObservable(e1.first()).toBe(expected)
33+
});
34+
35+
it('should go on forever on never', function() {
36+
var e2 = hot('--^-------');
37+
var expected = '--------';
38+
expectObservable(e2.first()).toBe(expected);
39+
});
40+
41+
it('should return first value that matches a predicate', function() {
42+
var e1 = hot('--a-^--b--c--a--c--|');
43+
var expected = '------(c|)';
44+
var predicate = function (value) {
45+
return value === 'c';
46+
};
47+
expectObservable(e1.first(predicate)).toBe(expected);
48+
});
49+
50+
it('should return first value that matches a predicate for odd numbers', function() {
51+
var e1 = hot('--a-^--b--c--d--e--|', {a: 1, b: 2, c: 3, d: 4, e: 5});
52+
var expected = '------(c|)';
53+
var predicate = function (value) {
54+
return value % 2 === 1;
55+
};
56+
expectObservable(e1.first(predicate)).toBe(expected, {c: 3});
57+
});
58+
59+
it('should return first value that matches a predicate using thisArg', function() {
60+
var e1 = hot('--a-^--b--c--d--e--|', {a: 1, b: 2, c: 3, d: 4, e: 5});
61+
var expected = '------(c|)';
62+
var predicate = function (value) {
63+
expect(this).toEqual(42);
64+
return value % 2 === 1;
65+
};
66+
expectObservable(e1.first(predicate, 42)).toBe(expected, {c: 3});
67+
});
68+
69+
it('should error when no value matches the predicate', function() {
70+
var e1 = hot('--a-^--b--c--a--c--|');
71+
var expected = '---------------#';
72+
var predicate = function (value) {
73+
return value === 's';
74+
};
75+
expectObservable(e1.first(predicate)).toBe(expected, null, new Rx.EmptyError());
76+
});
77+
78+
it('should return the default value when no value matches the predicate', function() {
79+
var e1 = hot('--a-^--b--c--a--c--|');
80+
var expected = '---------------(d|)';
81+
var predicate = function (value) {
82+
return value === 's';
83+
};
84+
expectObservable(e1.first(predicate, null, 'd')).toBe(expected);
85+
});
86+
87+
it('should propagate error when no value matches the predicate', function() {
88+
var e1 = hot('--a-^--b--c--a--#');
89+
var expected = '------------#';
90+
var predicate = function (value) {
91+
return value === 's';
92+
};
93+
expectObservable(e1.first(predicate)).toBe(expected);
94+
});
95+
96+
it('should return first value that matches the index in the predicate', function() {
97+
var e1 = hot('--a-^--b--c--a--c--|');
98+
var expected = '---------(a|)';
99+
var predicate = function (value, index) {
100+
return index === 2;
101+
};
102+
expectObservable(e1.first(predicate)).toBe(expected);
103+
});
104+
105+
it('should propagate error from predicate', function() {
106+
var e1 = hot('--a-^--b--c--d--e--|', {a: 1, b: 2, c: 3, d: 4, e: 5});
107+
var expected = '---------#';
108+
var predicate = function (value) {
109+
if (value < 4) {
110+
return false;
111+
} else {
112+
throw 'error';
113+
}
114+
};
115+
expectObservable(e1.first(predicate)).toBe(expected, null, 'error');
116+
});
117+
});

src/operators/first.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import Observable from '../Observable';
2+
import Operator from '../Operator';
3+
import Subscriber from '../Subscriber';
4+
import Observer from '../Observer';
5+
6+
import tryCatch from '../util/tryCatch';
7+
import {errorObject} from '../util/errorObject';
8+
import bindCallback from '../util/bindCallback';
9+
import EmptyError from '../util/EmptyError';
10+
11+
export default function first<T>(predicate?: (value: T, index: number, source: Observable<T>) => boolean,
12+
thisArg?: any,
13+
defaultValue?: any): Observable<T> {
14+
return this.lift(new FirstOperator(predicate, thisArg, defaultValue, this));
15+
}
16+
17+
class FirstOperator<T, R> implements Operator<T, R> {
18+
constructor(private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
19+
private thisArg?: any,
20+
private defaultValue?: any,
21+
private source?: Observable<T>) {
22+
}
23+
24+
call(observer: Subscriber<R>): Subscriber<T> {
25+
return new FirstSubscriber(
26+
observer, this.predicate, this.thisArg, this.defaultValue, this.source
27+
);
28+
}
29+
}
30+
31+
class FirstSubscriber<T> extends Subscriber<T> {
32+
private predicate: Function;
33+
private index: number = 0;
34+
private hasCompleted: boolean = false;
35+
36+
constructor(destination: Observer<T>,
37+
predicate?: (value: T, index: number, source: Observable<T>) => boolean,
38+
private thisArg?: any,
39+
private defaultValue?: any,
40+
private source?: Observable<T>) {
41+
super(destination);
42+
if (typeof predicate === 'function') {
43+
this.predicate = bindCallback(predicate, thisArg, 3);
44+
}
45+
}
46+
47+
_next(value: T) {
48+
const destination = this.destination;
49+
const predicate = this.predicate;
50+
let passed: any = true;
51+
if (predicate) {
52+
passed = tryCatch(predicate)(value, this.index++, this.source);
53+
if (passed === errorObject) {
54+
destination.error(passed.e);
55+
return;
56+
}
57+
}
58+
if (passed) {
59+
destination.next(value);
60+
destination.complete();
61+
this.hasCompleted = true;
62+
}
63+
}
64+
65+
_complete() {
66+
const destination = this.destination;
67+
if (!this.hasCompleted && typeof this.defaultValue !== 'undefined') {
68+
destination.next(this.defaultValue);
69+
destination.complete();
70+
} else if (!this.hasCompleted) {
71+
destination.error(new EmptyError);
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)