Skip to content

Commit 9f5d510

Browse files
committed
feat(operator): add window operators: window, windowWhen, windowTime, windowCount, windowToggle
closes #195
1 parent d565115 commit 9f5d510

File tree

14 files changed

+602
-9
lines changed

14 files changed

+602
-9
lines changed

spec/operators/window-spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* globals describe, it, expect */
2+
var Rx = require('../../dist/cjs/Rx');
3+
var Observable = Rx.Observable;
4+
5+
describe('Observable.prototype.window', function () {
6+
it('should emit windows that close and reopen', function (done) {
7+
var expected = [
8+
[0, 1, 2],
9+
[3, 4, 5],
10+
[6, 7, 8]
11+
];
12+
Observable.interval(100)
13+
.window(Observable.interval(320))
14+
.take(3)
15+
.flatMap(function (x) { return x.toArray(); })
16+
.subscribe(function (w) {
17+
expect(w).toEqual(expected.shift())
18+
}, null, done);
19+
}, 2000);
20+
});

spec/operators/windowCount-spec.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/* globals describe, it, expect */
2+
var Rx = require('../../dist/cjs/Rx');
3+
var Observable = Rx.Observable;
4+
5+
describe('Observable.prototype.windowCount', function () {
6+
it('should emit windows at intervals', function (done) {
7+
var expected = [
8+
[0, 1],
9+
[1, 2],
10+
[2, 3],
11+
[3]
12+
];
13+
Observable.range(0, 4)
14+
.windowCount(2, 1)
15+
.take(3)
16+
.flatMap(function (x) { return x.toArray(); })
17+
.subscribe(function (w) {
18+
expect(w).toEqual(expected.shift())
19+
}, null, done);
20+
}, 2000);
21+
});

spec/operators/windowTime-spec.js

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* globals describe, it, expect */
2+
var Rx = require('../../dist/cjs/Rx');
3+
var Observable = Rx.Observable;
4+
5+
describe('Observable.prototype.windowTime', function () {
6+
it('should emit windows at intervals', function (done) {
7+
var expected = [
8+
[0, 1, 2],
9+
[3, 4, 5],
10+
[6, 7, 8]
11+
];
12+
Observable.interval(100)
13+
.windowTime(320)
14+
.take(3)
15+
.flatMap(function (x) { return x.toArray(); })
16+
.subscribe(function (w) {
17+
expect(w).toEqual(expected.shift())
18+
}, null, done);
19+
}, 2000);
20+
21+
22+
it('should emit windows that have been created at intervals and close after the specified delay', function (done) {
23+
var expected = [
24+
[0, 1, 2, 3, 4],
25+
[2, 3, 4, 5, 6],
26+
[4, 5, 6, 7, 8]
27+
];
28+
Observable.interval(100)
29+
.windowTime(520, 220)
30+
.take(3)
31+
.flatMap(function (x) { return x.toArray(); })
32+
.subscribe(function (w) {
33+
expect(w).toEqual(expected.shift())
34+
}, null, done);
35+
}, 2000);
36+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* globals describe, it, expect */
2+
var Rx = require('../../dist/cjs/Rx');
3+
var Observable = Rx.Observable;
4+
5+
describe('Observable.prototype.windowToggle', function () {
6+
it('should emit windows that are opened by an observable from the first argument and closed by an observable returned by the function in the second argument', function (done) {
7+
Observable.interval(100).take(10)
8+
.windowToggle(Observable.timer(320).mapTo('test'), function (n) {
9+
expect(n).toBe('test');
10+
return Observable.timer(320);
11+
})
12+
.flatMap(function (w) { return w.toArray(); })
13+
.subscribe(function (w) {
14+
expect(w).toEqual([3, 4, 5])
15+
}, null, done);
16+
}, 2000);
17+
});

spec/operators/windowWhen-spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/* globals describe, it, expect */
2+
var Rx = require('../../dist/cjs/Rx');
3+
var Observable = Rx.Observable;
4+
5+
describe('Observable.prototype.windowWhen', function () {
6+
it('should emit windows that close and reopen', function (done) {
7+
var expected = [
8+
[0, 1, 2],
9+
[3, 4, 5],
10+
[6, 7, 8]
11+
];
12+
Observable.interval(100)
13+
.windowWhen(function () { return Observable.timer(320); })
14+
.take(3)
15+
.flatMap(function (x) { return x.toArray(); })
16+
.subscribe(function (w) {
17+
expect(w).toEqual(expected.shift())
18+
}, null, done);
19+
}, 2000);
20+
});

src/Observable.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ export default class Observable<T> {
162162
repeat: <T>(count: number) => Observable<T>;
163163

164164
groupBy: <T, R>(keySelector: (value:T) => string, durationSelector?: (group:GroupSubject<R>) => Observable<any>, elementSelector?: (value:T) => R) => Observable<R>;
165-
165+
window: <T>(closingNotifier: Observable<any>) => Observable<Observable<T>>;
166+
windowWhen: <T>(closingSelector: () => Observable<any>) => Observable<Observable<T>>;
167+
windowToggle: <T, O>(openings: Observable<O>, closingSelector?: (openValue: O) => Observable<any>) => Observable<Observable<T>>
168+
windowTime: <T>(windowTimeSpan: number, windowCreationInterval?: number, scheduler?: Scheduler) => Observable<Observable<T>>;
169+
windowCount: <T>(windowSize: number, startWindowEvery: number) => Observable<Observable<T>>;
170+
166171
finally: (ensure: () => void, thisArg?: any) => Observable<T>;
167172
}

src/Rx.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,18 @@ import _finally from './operators/finally';
160160
observableProto.finally = _finally;
161161

162162
import groupBy from './operators/groupBy';
163+
import window from './operators/window';
164+
import windowWhen from './operators/windowWhen';
165+
import windowToggle from './operators/windowToggle';
166+
import windowTime from './operators/windowTime';
167+
import windowCount from './operators/windowCount';
163168

164169
observableProto.groupBy = groupBy;
170+
observableProto.window = window;
171+
observableProto.windowWhen = windowWhen;
172+
observableProto.windowToggle = windowToggle;
173+
observableProto.windowTime = windowTime;
174+
observableProto.windowCount = windowCount;
165175

166176
import delay from './operators/delay';
167177
import throttle from './operators/throttle';

src/Scheduler.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ export class Action<T> extends Subscription<T> {
6666
}
6767

6868
schedule(state?:any): Action<T> {
69+
if (this.isUnsubscribed) {
70+
return this;
71+
}
72+
6973
this.state = state;
7074
const scheduler = this.scheduler;
7175
scheduler.actions.push(this);
@@ -103,6 +107,9 @@ export class NextTickAction<T> extends Action<T> {
103107
id: number;
104108

105109
schedule(state?:any): Action<T> {
110+
if (this.isUnsubscribed) {
111+
return this;
112+
}
106113

107114
this.state = state;
108115

@@ -151,7 +158,10 @@ export class FutureAction<T> extends Action<T> {
151158
}
152159

153160
schedule(state?:any): Action<T> {
154-
161+
if (this.isUnsubscribed) {
162+
return this;
163+
}
164+
155165
this.state = state;
156166

157167
const id = this.id;

src/operators/window.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
7+
import tryCatch from '../util/tryCatch';
8+
import {errorObject} from '../util/errorObject';
9+
import bindCallback from '../util/bindCallback';
10+
11+
export default function window<T>(closingNotifier: Observable<any>) : Observable<Observable<T>> {
12+
return this.lift(new WindowOperator(closingNotifier));
13+
}
14+
15+
export class WindowOperator<T, R> implements Operator<T, R> {
16+
17+
constructor(private closingNotifier: Observable<any>) {
18+
}
19+
20+
call(observer: Observer<T>): Observer<T> {
21+
return new WindowSubscriber(observer, this.closingNotifier);
22+
}
23+
}
24+
25+
export class WindowSubscriber<T> extends Subscriber<T> {
26+
private window: Subject<T> = new Subject<T>();
27+
28+
constructor(destination: Observer<T>, private closingNotifier: Observable<any>) {
29+
super(destination);
30+
this.add(closingNotifier.subscribe(new WindowClosingNotifierSubscriber(this)));
31+
this.openWindow();
32+
}
33+
34+
_next(value: T) {
35+
this.window.next(value);
36+
}
37+
38+
_error(err: any) {
39+
this.window.error(err);
40+
this.destination.error(err);
41+
}
42+
43+
_complete() {
44+
this.window.complete();
45+
this.destination.complete();
46+
}
47+
48+
openWindow() {
49+
const prevWindow = this.window;
50+
if (prevWindow) {
51+
prevWindow.complete();
52+
}
53+
this.destination.next(this.window = new Subject<T>());
54+
}
55+
}
56+
57+
export class WindowClosingNotifierSubscriber<T> extends Subscriber<T> {
58+
constructor(private parent: WindowSubscriber<any>) {
59+
super(null);
60+
}
61+
62+
_next() {
63+
this.parent.openWindow();
64+
}
65+
}

src/operators/windowCount.ts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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+
import Scheduler from '../Scheduler';
8+
9+
import tryCatch from '../util/tryCatch';
10+
import {errorObject} from '../util/errorObject';
11+
import bindCallback from '../util/bindCallback';
12+
13+
export default function windowCount<T>(windowSize: number, startWindowEvery: number = 0) : Observable<Observable<T>> {
14+
return this.lift(new WindowCountOperator(windowSize, startWindowEvery));
15+
}
16+
17+
export class WindowCountOperator<T, R> implements Operator<T, R> {
18+
19+
constructor(private windowSize: number, private startWindowEvery: number) {
20+
}
21+
22+
call(observer: Observer<T>): Observer<T> {
23+
return new WindowCountSubscriber(observer, this.windowSize, this.startWindowEvery);
24+
}
25+
}
26+
27+
export class WindowCountSubscriber<T> extends Subscriber<T> {
28+
private windows: { count: number, window: Subject<T> } [] = [];
29+
private count: number = 0;
30+
31+
constructor(destination: Observer<T>, private windowSize: number, private startWindowEvery: number) {
32+
super(destination);
33+
}
34+
35+
_next(value: T) {
36+
const count = (this.count += 1);
37+
const startWindowEvery = this.startWindowEvery;
38+
const windowSize = this.windowSize;
39+
const windows = this.windows;
40+
41+
if (startWindowEvery && count % this.startWindowEvery === 0) {
42+
let window = new Subject<T>();
43+
windows.push({ count: 0, window });
44+
this.destination.next(window);
45+
}
46+
47+
const len = windows.length;
48+
for (let i = 0; i < len; i++) {
49+
let w = windows[i];
50+
const window = w.window;
51+
window.next(value);
52+
if (windowSize === (w.count += 1)) {
53+
window.complete();
54+
}
55+
}
56+
}
57+
58+
_error(err: any) {
59+
const windows = this.windows;
60+
while (windows.length > 0) {
61+
windows.shift().window.error(err);
62+
}
63+
this.destination.error(err);
64+
}
65+
66+
_complete() {
67+
const windows = this.windows;
68+
while (windows.length > 0) {
69+
windows.shift().window.complete();
70+
}
71+
this.destination.complete();
72+
}
73+
}

0 commit comments

Comments
 (0)