Skip to content

Commit

Permalink
feat(switchMap|switchMapTo): simplify interface
Browse files Browse the repository at this point in the history
- removes resultSelector argument from `switchMap` and `switchMapTo`
- updates tests

BREAKING CHANGE: `switchMap` and `switchMapTo` no longer take `resultSelector` arguments, to get the same functionality use `switchMap` and `map` in combination: `source.pipe(switchMap(x => of(x + x).pipe(y => x + y)))`.
  • Loading branch information
benlesh committed Mar 2, 2018
1 parent 4d2338b commit 959fb6a
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 226 deletions.
57 changes: 0 additions & 57 deletions spec/operators/switchMap-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,6 @@ describe('Observable.prototype.switchMap', () => {
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

it('should switch with a selector function', (done) => {
const a = Observable.of(1, 2, 3);
const expected = ['a1', 'b1', 'c1', 'a2', 'b2', 'c2', 'a3', 'b3', 'c3'];
a.switchMap((x) => Observable.of('a' + x, 'b' + x, 'c' + x))
.subscribe((x) => {
expect(x).to.equal(expected.shift());
}, null, done);
});

it('should unsub inner observables', () => {
const unsubbed: string[] = [];

Expand Down Expand Up @@ -76,24 +67,6 @@ describe('Observable.prototype.switchMap', () => {
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

it('should raise error when resultSelector throws', () => {
const x = cold( '--a--b--c--d--e--| ');
const xsubs = ' ^ ! ';
const e1 = hot('---------x---------y---------|');
const e1subs = '^ ! ';
const expected = '-----------# ';

function selector() {
throw 'error';
}

const result = e1.switchMap((value) => x, selector);

expectObservable(result).toBe(expected);
expectSubscriptions(x.subscriptions).toBe(xsubs);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

it('should switch inner cold observables, outer is unsubscribed early', () => {
const x = cold( '--a--b--c--d--e--| ');
const xsubs = ' ^ ! ';
Expand Down Expand Up @@ -356,34 +329,4 @@ describe('Observable.prototype.switchMap', () => {
expectSubscriptions(x.subscriptions).toBe(xsubs);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

it('should switch with resultSelector goodness', () => {
const x = cold( '--a--b--c--d--e--| ');
const xsubs = ' ^ ! ';
const y = cold( '---f---g---h---i--|');
const ysubs = ' ^ !';
const e1 = hot('---------x---------y---------| ');
const e1subs = '^ !';
const expected = '-----------a--b--c----f---g---h---i--|';

const observableLookup = { x: x, y: y };

const expectedValues = {
a: ['x', 'a', 0, 0],
b: ['x', 'b', 0, 1],
c: ['x', 'c', 0, 2],
f: ['y', 'f', 1, 0],
g: ['y', 'g', 1, 1],
h: ['y', 'h', 1, 2],
i: ['y', 'i', 1, 3]
};

const result = e1.switchMap((value) => observableLookup[value],
(innerValue, outerValue, innerIndex, outerIndex) => [innerValue, outerValue, innerIndex, outerIndex]);

expectObservable(result).toBe(expected, expectedValues);
expectSubscriptions(x.subscriptions).toBe(xsubs);
expectSubscriptions(y.subscriptions).toBe(ysubs);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
});
42 changes: 0 additions & 42 deletions spec/operators/switchMapTo-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,46 +222,4 @@ describe('Observable.prototype.switchMapTo', () => {
expectObservable(e1.switchMapTo(Observable.of('foo'))).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

it('should switch with resultSelector goodness', () => {
const x = cold( '--1--2--3--4--5--| ');
const xsubs = [' ^ ! ',
// --1--2--3--4--5--|
' ^ !'];
const e1 = hot('---------x---------y---------| ');
const e1subs = '^ !';
const expected = '-----------a--b--c---d--e--f--g--h--|';
const expectedValues = {
a: ['x', '1', 0, 0],
b: ['x', '2', 0, 1],
c: ['x', '3', 0, 2],
d: ['y', '1', 1, 0],
e: ['y', '2', 1, 1],
f: ['y', '3', 1, 2],
g: ['y', '4', 1, 3],
h: ['y', '5', 1, 4]
};

const result = e1.switchMapTo(x, (a, b, ai, bi) => [a, b, ai, bi]);

expectObservable(result).toBe(expected, expectedValues);
expectSubscriptions(x.subscriptions).toBe(xsubs);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

it('should raise error when resultSelector throws', () => {
const x = cold( '--1--2--3--4--5--| ');
const xsubs = ' ^ ! ';
const e1 = hot('---------x---------y---------|');
const e1subs = '^ !';
const expected = '-----------#';

const result = e1.switchMapTo(x, () => {
throw 'error';
});

expectObservable(result).toBe(expected);
expectSubscriptions(x.subscriptions).toBe(xsubs);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});
});
63 changes: 16 additions & 47 deletions src/internal/operators/switchMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ import { InnerSubscriber } from '../InnerSubscriber';
import { subscribeToResult } from '../util/subscribeToResult';
import { ObservableInput, OperatorFunction } from '../types';

/* tslint:disable:max-line-length */
export function switchMap<T, R>(project: (value: T, index: number) => ObservableInput<R>): OperatorFunction<T, R>;
export function switchMap<T, I, R>(project: (value: T, index: number) => ObservableInput<I>, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R): OperatorFunction<T, R>;
/* tslint:enable:max-line-length */

/**
* Projects each source value to an Observable which is merged in the output
* Observable, emitting values only from the most recently projected Observable.
Expand Down Expand Up @@ -44,37 +39,27 @@ export function switchMap<T, I, R>(project: (value: T, index: number) => Observa
* @param {function(value: T, ?index: number): ObservableInput} project A function
* that, when applied to an item emitted by the source Observable, returns an
* Observable.
* @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector]
* A function to produce the value on the output Observable based on the values
* and the indices of the source (outer) emission and the inner Observable
* emission. The arguments passed to this function are:
* - `outerValue`: the value that came from the source
* - `innerValue`: the value that came from the projected Observable
* - `outerIndex`: the "index" of the value that came from the source
* - `innerIndex`: the "index" of the value from the projected Observable
* @return {Observable} An Observable that emits the result of applying the
* projection function (and the optional `resultSelector`) to each item emitted
* by the source Observable and taking only the values from the most recently
* projected inner Observable.
* @method switchMap
* @owner Observable
*/
export function switchMap<T, I, R>(
project: (value: T, index: number) => ObservableInput<I>,
resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R
): OperatorFunction<T, I | R> {
return function switchMapOperatorFunction(source: Observable<T>): Observable<I | R> {
return source.lift(new SwitchMapOperator(project, resultSelector));
export function switchMap<T, R>(
project: (value: T, index: number) => ObservableInput<R>
): OperatorFunction<T, R> {
return function switchMapOperatorFunction(source: Observable<T>): Observable<R | R> {
return source.lift(new SwitchMapOperator(project));
};
}

class SwitchMapOperator<T, I, R> implements Operator<T, I> {
constructor(private project: (value: T, index: number) => ObservableInput<I>,
private resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R) {
class SwitchMapOperator<T, R> implements Operator<T, R> {
constructor(private project: (value: T, index: number) => ObservableInput<R>) {
}

call(subscriber: Subscriber<I>, source: any): any {
return source.subscribe(new SwitchMapSubscriber(subscriber, this.project, this.resultSelector));
call(subscriber: Subscriber<R>, source: any): any {
return source.subscribe(new SwitchMapSubscriber(subscriber, this.project));
}
}

Expand All @@ -83,18 +68,17 @@ class SwitchMapOperator<T, I, R> implements Operator<T, I> {
* @ignore
* @extends {Ignored}
*/
class SwitchMapSubscriber<T, I, R> extends OuterSubscriber<T, I> {
class SwitchMapSubscriber<T, R> extends OuterSubscriber<T, R> {
private index: number = 0;
private innerSubscription: Subscription;

constructor(destination: Subscriber<I>,
private project: (value: T, index: number) => ObservableInput<I>,
private resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R) {
constructor(destination: Subscriber<R>,
private project: (value: T, index: number) => ObservableInput<R>) {
super(destination);
}

protected _next(value: T) {
let result: ObservableInput<I>;
let result: ObservableInput<R>;
const index = this.index++;
try {
result = this.project(value, index);
Expand All @@ -105,7 +89,7 @@ class SwitchMapSubscriber<T, I, R> extends OuterSubscriber<T, I> {
this._innerSub(result, value, index);
}

private _innerSub(result: ObservableInput<I>, value: T, index: number) {
private _innerSub(result: ObservableInput<R>, value: T, index: number) {
const innerSubscription = this.innerSubscription;
if (innerSubscription) {
innerSubscription.unsubscribe();
Expand All @@ -132,24 +116,9 @@ class SwitchMapSubscriber<T, I, R> extends OuterSubscriber<T, I> {
}
}

notifyNext(outerValue: T, innerValue: I,
notifyNext(outerValue: T, innerValue: R,
outerIndex: number, innerIndex: number,
innerSub: InnerSubscriber<T, I>): void {
if (this.resultSelector) {
this._tryNotifyNext(outerValue, innerValue, outerIndex, innerIndex);
} else {
innerSub: InnerSubscriber<T, R>): void {
this.destination.next(innerValue);
}
}

private _tryNotifyNext(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): void {
let result: R;
try {
result = this.resultSelector(outerValue, innerValue, outerIndex, innerIndex);
} catch (err) {
this.destination.error(err);
return;
}
this.destination.next(result);
}
}
61 changes: 11 additions & 50 deletions src/internal/operators/switchMapTo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@ import { InnerSubscriber } from '../InnerSubscriber';
import { subscribeToResult } from '../util/subscribeToResult';
import { ObservableInput, OperatorFunction } from '../types';

/* tslint:disable:max-line-length */
export function switchMapTo<T, R>(observable: ObservableInput<R>): OperatorFunction<T, R>;
export function switchMapTo<T, I, R>(observable: ObservableInput<I>, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R): OperatorFunction<T, R>;
/* tslint:enable:max-line-length */

/**
* Projects each source value to the same Observable which is flattened multiple
* times with {@link switch} in the output Observable.
Expand Down Expand Up @@ -39,36 +34,23 @@ export function switchMapTo<T, I, R>(observable: ObservableInput<I>, resultSelec
*
* @param {ObservableInput} innerObservable An Observable to replace each value from
* the source Observable.
* @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector]
* A function to produce the value on the output Observable based on the values
* and the indices of the source (outer) emission and the inner Observable
* emission. The arguments passed to this function are:
* - `outerValue`: the value that came from the source
* - `innerValue`: the value that came from the projected Observable
* - `outerIndex`: the "index" of the value that came from the source
* - `innerIndex`: the "index" of the value from the projected Observable
* @return {Observable} An Observable that emits items from the given
* `innerObservable` (and optionally transformed through `resultSelector`) every
* time a value is emitted on the source Observable, and taking only the values
* from the most recently projected inner Observable.
* @method switchMapTo
* @owner Observable
*/
export function switchMapTo<T, I, R>(innerObservable: ObservableInput<I>,
resultSelector?: (outerValue: T,
innerValue: I,
outerIndex: number,
innerIndex: number) => R): OperatorFunction<T, I | R> {
return (source: Observable<T>) => source.lift(new SwitchMapToOperator(innerObservable, resultSelector));
export function switchMapTo<T, R>(innerObservable: ObservableInput<R>): OperatorFunction<T, R> {
return (source: Observable<T>) => source.lift(new SwitchMapToOperator(innerObservable));
}

class SwitchMapToOperator<T, I, R> implements Operator<T, I> {
constructor(private observable: ObservableInput<I>,
private resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R) {
class SwitchMapToOperator<T, R> implements Operator<T, R> {
constructor(private observable: ObservableInput<R>) {
}

call(subscriber: Subscriber<I>, source: any): any {
return source.subscribe(new SwitchMapToSubscriber(subscriber, this.observable, this.resultSelector));
call(subscriber: Subscriber<R>, source: any): any {
return source.subscribe(new SwitchMapToSubscriber(subscriber, this.observable));
}
}

Expand All @@ -77,13 +59,11 @@ class SwitchMapToOperator<T, I, R> implements Operator<T, I> {
* @ignore
* @extends {Ignored}
*/
class SwitchMapToSubscriber<T, I, R> extends OuterSubscriber<T, I> {
class SwitchMapToSubscriber<T, R> extends OuterSubscriber<T, R> {
private index: number = 0;
private innerSubscription: Subscription;

constructor(destination: Subscriber<I>,
private inner: ObservableInput<I>,
private resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R) {
constructor(destination: Subscriber<R>, private inner: ObservableInput<R>) {
super(destination);
}

Expand Down Expand Up @@ -114,28 +94,9 @@ class SwitchMapToSubscriber<T, I, R> extends OuterSubscriber<T, I> {
}
}

notifyNext(outerValue: T, innerValue: I,
notifyNext(outerValue: T, innerValue: R,
outerIndex: number, innerIndex: number,
innerSub: InnerSubscriber<T, I>): void {
const { resultSelector, destination } = this;
if (resultSelector) {
this.tryResultSelector(outerValue, innerValue, outerIndex, innerIndex);
} else {
destination.next(innerValue);
}
}

private tryResultSelector(outerValue: T, innerValue: I,
outerIndex: number, innerIndex: number): void {
const { resultSelector, destination } = this;
let result: R;
try {
result = resultSelector(outerValue, innerValue, outerIndex, innerIndex);
} catch (err) {
destination.error(err);
return;
}

destination.next(result);
innerSub: InnerSubscriber<T, R>): void {
this.destination.next(innerValue);
}
}
18 changes: 2 additions & 16 deletions src/internal/patching/operator/switchMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,6 @@ import { Observable } from '../../Observable';
import { ObservableInput } from '../../types';
import { switchMap as higherOrderSwitchMap } from '../../operators/switchMap';

/* tslint:disable:max-line-length */
export function switchMap<T, R>(this: Observable<T>, project: (value: T, index: number) => ObservableInput<R>): Observable<R>;
export function switchMap<T, I, R>(this: Observable<T>, project: (value: T, index: number) => ObservableInput<I>, resultSelector: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R): Observable<R>;
/* tslint:enable:max-line-length */

/**
* Projects each source value to an Observable which is merged in the output
* Observable, emitting values only from the most recently projected Observable.
Expand Down Expand Up @@ -40,22 +35,13 @@ export function switchMap<T, I, R>(this: Observable<T>, project: (value: T, inde
* @param {function(value: T, ?index: number): ObservableInput} project A function
* that, when applied to an item emitted by the source Observable, returns an
* Observable.
* @param {function(outerValue: T, innerValue: I, outerIndex: number, innerIndex: number): any} [resultSelector]
* A function to produce the value on the output Observable based on the values
* and the indices of the source (outer) emission and the inner Observable
* emission. The arguments passed to this function are:
* - `outerValue`: the value that came from the source
* - `innerValue`: the value that came from the projected Observable
* - `outerIndex`: the "index" of the value that came from the source
* - `innerIndex`: the "index" of the value from the projected Observable
* @return {Observable} An Observable that emits the result of applying the
* projection function (and the optional `resultSelector`) to each item emitted
* by the source Observable and taking only the values from the most recently
* projected inner Observable.
* @method switchMap
* @owner Observable
*/
export function switchMap<T, I, R>(this: Observable<T>, project: (value: T, index: number) => ObservableInput<I>,
resultSelector?: (outerValue: T, innerValue: I, outerIndex: number, innerIndex: number) => R): Observable<I | R> {
return higherOrderSwitchMap(project, resultSelector)(this);
export function switchMap<T, R>(this: Observable<T>, project: (value: T, index: number) => ObservableInput<R>): Observable<R> {
return higherOrderSwitchMap(project)(this);
}
Loading

0 comments on commit 959fb6a

Please sign in to comment.