Skip to content

Commit 322218a

Browse files
committed
feat(operator): add withLatestFrom
closes #209
1 parent 2239313 commit 322218a

File tree

4 files changed

+102
-1
lines changed

4 files changed

+102
-1
lines changed

spec/operators/withLatestFrom-spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/* globals describe, it, expect */
2+
var Rx = require('../../dist/cjs/Rx');
3+
var Observable = Rx.Observable;
4+
5+
describe('Observable.prototype.withLatestFrom()', function () {
6+
it('should merge the emitted value with the latest values of the other observables', function (done) {
7+
var a = Observable.of('a');
8+
var b = Observable.of('b', 'c');
9+
10+
Observable.value('d').delay(100)
11+
.withLatestFrom(a, b, function (x, a, b) { return [x, a, b]; })
12+
.subscribe(function (x) {
13+
expect(x).toEqual(['d', 'a', 'c']);
14+
}, null, done);
15+
});
16+
17+
it('should emit nothing if the other observables never emit', function (done) {
18+
var a = Observable.of('a');
19+
var b = Observable.never();
20+
21+
Observable.value('d').delay(100)
22+
.withLatestFrom(a, b, function (x, a, b) { return [x, a, b]; })
23+
.subscribe(function (x) {
24+
expect('this was called').toBe(false);
25+
}, null, done);
26+
});
27+
});

src/Observable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export default class Observable<T> {
120120
static combineLatest: <T>(...observables: (Observable<any> | ((...values: Array<any>) => T)) []) => Observable<T>;
121121
combineLatest: <R>(...observables: (Observable<any> | ((...values: Array<any>) => R)) []) => Observable<R>;
122122
combineAll: <R>(project?: (...values: Array<any>) => R) => Observable<R>;
123-
123+
withLatestFrom: <R>(...observables: (Observable<any> | ((...values: Array<any>) => R)) []) => Observable<R>;
124124
static zip: <T>(...observables: (Observable<any> | ((...values: Array<any>) => T)) []) => Observable<T>;
125125
zip: <R>(...observables: (Observable<any> | ((...values: Array<any>) => R)) []) => Observable<R>;
126126
zipAll: <R>(project?: (...values: Array<any>) => R) => Observable<R>;

src/Rx.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,10 +111,12 @@ observableProto.distinctUntilKeyChanged = distinctUntilKeyChanged;
111111

112112
import {combineLatest, combineLatestProto} from './operators/combineLatest';
113113
import combineAll from './operators/combineAll';
114+
import withLatestFrom from './operators/withLatestFrom';
114115

115116
Observable.combineLatest = combineLatest;
116117
observableProto.combineLatest = combineLatestProto;
117118
observableProto.combineAll = combineAll;
119+
observableProto.withLatestFrom = withLatestFrom;
118120

119121
import {zip, zipProto} from './operators/zip';
120122
import zipAll from './operators/zipAll';

src/operators/withLatestFrom.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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 withLatestFrom<R>(...args: (Observable<any>|((...values: any[]) => Observable<R>))[]): Observable<R> {
10+
const project = <((...values: any[]) => Observable<R>)>args.pop();
11+
const observables = <Observable<any>[]>args;
12+
return this.lift(new WithLatestFromOperator(observables, project));
13+
}
14+
15+
export class WithLatestFromOperator<T, R> implements Operator<T, R> {
16+
constructor(private observables: Observable<any>[], private project: (...values: any[]) => Observable<R>) {
17+
}
18+
19+
call(observer: Observer<R>): Observer<T> {
20+
return new WithLatestFromSubscriber<T, R>(observer, this.observables, this.project);
21+
}
22+
}
23+
24+
export class WithLatestFromSubscriber<T, R> extends Subscriber<T> {
25+
private values: any[];
26+
private toSet: number;
27+
28+
constructor(destination: Observer<T>, private observables: Observable<any>[], private project: (...values: any[]) => Observable<R>) {
29+
super(destination);
30+
const len = observables.length;
31+
this.values = new Array(len);
32+
this.toSet = len;
33+
for (let i = 0; i < len; i++) {
34+
this.add(observables[i].subscribe(new WithLatestInnerSubscriber(this, i)))
35+
}
36+
}
37+
38+
notifyValue(index, value) {
39+
this.values[index] = value;
40+
this.toSet--;
41+
}
42+
43+
_next(value: T) {
44+
if (this.toSet === 0) {
45+
const values = this.values;
46+
let result = tryCatch(this.project)([value, ...values]);
47+
if (result === errorObject) {
48+
this.destination.error(result.e);
49+
} else {
50+
this.destination.next(result);
51+
}
52+
}
53+
}
54+
}
55+
56+
export class WithLatestInnerSubscriber<T, R> extends Subscriber<T> {
57+
constructor(private parent: WithLatestFromSubscriber<T, R>, private valueIndex: number) {
58+
super(null)
59+
}
60+
61+
_next(value: T) {
62+
this.parent.notifyValue(this.valueIndex, value);
63+
}
64+
65+
_error(err: any) {
66+
this.parent.error(err);
67+
}
68+
69+
_complete() {
70+
// noop
71+
}
72+
}

0 commit comments

Comments
 (0)