Skip to content

Commit

Permalink
feat(last): simplify interface
Browse files Browse the repository at this point in the history
- Removes resultSelector argument
- Updates tests

BREAKING CHANGE: no longer accepts `resultSelector` argument. To get this same functionality, use `map`.
  • Loading branch information
benlesh committed Mar 2, 2018
1 parent a011338 commit 3240419
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 131 deletions.
46 changes: 3 additions & 43 deletions spec/operators/last-spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect } from 'chai';

import * as Rx from '../../src/Rx';
import { hot, cold, expectObservable, expectSubscriptions } from '../helpers/marble-testing';

Expand Down Expand Up @@ -87,7 +87,7 @@ describe('Observable.prototype.last', () => {
const e1subs = '(^!)';
const expected = '(a|)';

expectObservable(e1.last(null, null, 'a')).toBe(expected);
expectObservable(e1.last(null, 'a')).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

Expand All @@ -96,23 +96,7 @@ describe('Observable.prototype.last', () => {
const e1subs = '^ !';
const expected = '----------------(d|)';

expectObservable(e1.last(null, null, 'x')).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

it('should support a result selector argument', () => {
const e1 = hot('--a--^---b---c---d---e--|');
const e1subs = '^ !';
const expected = '-------------------(x|)';

const predicate = function (x) { return x === 'c'; };
const resultSelector = function (x, i) {
expect(i).to.equal(1);
expect(x).to.equal('c');
return 'x';
};

expectObservable(e1.last(predicate, resultSelector)).toBe(expected);
expectObservable(e1.last(null, 'x')).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

Expand All @@ -133,20 +117,6 @@ describe('Observable.prototype.last', () => {
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

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

const predicate = function (x) { return x === 'c'; };
const resultSelector = function (x, i) {
throw 'error';
};

expectObservable(e1.last(predicate, resultSelector)).toBe(expected);
expectSubscriptions(e1.subscriptions).toBe(e1subs);
});

it('should support type guards without breaking previous behavior', () => {
// tslint:disable no-unused-variable

Expand Down Expand Up @@ -203,16 +173,6 @@ describe('Observable.prototype.last', () => {
// boolean predicates preserve the type
xs.last(x => typeof x === 'string')
.subscribe(x => x); // x is still string | number
xs.last(x => !!x, x => x)
.subscribe(x => x); // x is still string | number
xs.last(x => typeof x === 'string', x => x, '') // default is string; x remains string | number
.subscribe(x => x); // x is still string | number

// `last` still uses the `resultSelector` return type, if it exists.
xs.last(x => typeof x === 'string', x => ({ str: `${x}` })) // x remains string | number
.subscribe(o => o.str); // o is { str: string }
xs.last(x => typeof x === 'string', x => ({ str: `${x}` }), { str: '' })
.subscribe(o => o.str); // o is { str: string }
}

// tslint:disable enable
Expand Down
83 changes: 22 additions & 61 deletions src/internal/operators/last.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
import { Observable } from '../Observable';
import { Operator } from '../Operator';
import { Subscriber } from '../Subscriber';
import { EmptyError } from '../util/EmptyError';
import { OperatorFunction, MonoTypeOperatorFunction } from '../types';

/* tslint:disable:max-line-length */
export function last<T, S extends T>(predicate: (value: T, index: number, source: Observable<T>) => value is S): OperatorFunction<T, S>;
export function last<T, S extends T, R>(predicate: (value: T | S, index: number, source: Observable<T>) => value is S,
resultSelector: (value: S, index: number) => R, defaultValue?: R): OperatorFunction<T, R>;
export function last<T, S extends T>(predicate: (value: T, index: number, source: Observable<T>) => value is S,
resultSelector: void,
defaultValue?: S): OperatorFunction<T, S>;
export function last<T>(predicate?: (value: T, index: number, source: Observable<T>) => boolean): MonoTypeOperatorFunction<T>;
export function last<T, R>(predicate: (value: T, index: number, source: Observable<T>) => boolean,
resultSelector?: (value: T, index: number) => R,
defaultValue?: R): OperatorFunction<T, R>;
export function last<T>(predicate: (value: T, index: number, source: Observable<T>) => boolean,
resultSelector: void,
defaultValue?: T): MonoTypeOperatorFunction<T>;
/* tslint:enable:max-line-length */
import { EmptyError } from '..//util/EmptyError';
import { MonoTypeOperatorFunction } from '../../internal/types';

/**
* Returns an Observable that emits only the last item emitted by the source Observable.
Expand All @@ -30,28 +14,26 @@ export function last<T>(predicate: (value: T, index: number, source: Observable<
*
* @throws {EmptyError} Delivers an EmptyError to the Observer's `error`
* callback if the Observable completes before any `next` notification was sent.
* @param {function} predicate - The condition any source emitted item has to satisfy.
* @param {function} [predicate] - The condition any source emitted item has to satisfy.
* @param {any} [defaultValue] - An optional default value to provide if last
* predicate isn't met or no values were emitted.
* @return {Observable} An Observable that emits only the last item satisfying the given condition
* from the source, or an NoSuchElementException if no such items are emitted.
* @throws - Throws if no items that match the predicate are emitted by the source Observable.
* @method last
* @owner Observable
*/
export function last<T, R>(predicate?: (value: T, index: number, source: Observable<T>) => boolean,
resultSelector?: ((value: T, index: number) => R) | void,
defaultValue?: R): OperatorFunction<T, T | R> {
return (source: Observable<T>) => source.lift(new LastOperator(predicate, resultSelector, defaultValue, source));
export function last<T>(predicate?: (value: T, index: number, source: Observable<T>) => boolean,
defaultValue?: T): MonoTypeOperatorFunction<T> {
return (source: Observable<T>) => source.lift(new LastOperator(predicate, defaultValue, source));
}

class LastOperator<T, R> implements Operator<T, R> {
constructor(private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
private resultSelector?: ((value: T, index: number) => R) | void,
private defaultValue?: any,
private source?: Observable<T>) {
class LastOperator<T> implements Operator<T, T> {
constructor(private predicate: (value: T, index: number, source: Observable<T>) => boolean,
private defaultValue: any,
private source: Observable<T>) {
}

call(observer: Subscriber<R>, source: any): any {
return source.subscribe(new LastSubscriber(observer, this.predicate, this.resultSelector, this.defaultValue, this.source));
call(observer: Subscriber<T>, source: any): any {
return source.subscribe(new LastSubscriber(observer, this.predicate, this.defaultValue, this.source));
}
}

Expand All @@ -60,16 +42,15 @@ class LastOperator<T, R> implements Operator<T, R> {
* @ignore
* @extends {Ignored}
*/
class LastSubscriber<T, R> extends Subscriber<T> {
private lastValue: T | R;
private hasValue: boolean = false;
private index: number = 0;
class LastSubscriber<T> extends Subscriber<T> {
private lastValue: T;
private hasValue = false;
private index = 0;

constructor(destination: Subscriber<R>,
private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
private resultSelector?: ((value: T, index: number) => R) | void,
private defaultValue?: any,
private source?: Observable<T>) {
constructor(destination: Subscriber<T>,
private predicate: (value: T, index: number, source: Observable<T>) => boolean,
private defaultValue: T,
private source: Observable<T>) {
super(destination);
if (typeof defaultValue !== 'undefined') {
this.lastValue = defaultValue;
Expand All @@ -82,10 +63,6 @@ class LastSubscriber<T, R> extends Subscriber<T> {
if (this.predicate) {
this._tryPredicate(value, index);
} else {
if (this.resultSelector) {
this._tryResultSelector(value, index);
return;
}
this.lastValue = value;
this.hasValue = true;
}
Expand All @@ -100,27 +77,11 @@ class LastSubscriber<T, R> extends Subscriber<T> {
return;
}
if (result) {
if (this.resultSelector) {
this._tryResultSelector(value, index);
return;
}
this.lastValue = value;
this.hasValue = true;
}
}

private _tryResultSelector(value: T, index: number) {
let result: any;
try {
result = (<any>this).resultSelector(value, index);
} catch (err) {
this.destination.error(err);
return;
}
this.lastValue = result;
this.hasValue = true;
}

protected _complete(): void {
const destination = this.destination;
if (this.hasValue) {
Expand Down
33 changes: 6 additions & 27 deletions src/internal/patching/operator/last.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,6 @@
import { Observable } from '../../Observable';
import { last as higherOrder } from '../../operators/last';

/* tslint:disable:max-line-length */
export function last<T, S extends T>(this: Observable<T>,
predicate: (value: T, index: number, source: Observable<T>) => value is S): Observable<S>;
export function last<T, S extends T, R>(this: Observable<T>,
predicate: (value: T | S, index: number, source: Observable<T>) => value is S,
resultSelector: (value: S, index: number) => R, defaultValue?: R): Observable<R>;
export function last<T, S extends T>(this: Observable<T>,
predicate: (value: T, index: number, source: Observable<T>) => value is S,
resultSelector: void,
defaultValue?: S): Observable<S>;
export function last<T>(this: Observable<T>,
predicate?: (value: T, index: number, source: Observable<T>) => boolean): Observable<T>;
export function last<T, R>(this: Observable<T>,
predicate: (value: T, index: number, source: Observable<T>) => boolean,
resultSelector?: (value: T, index: number) => R,
defaultValue?: R): Observable<R>;
export function last<T>(this: Observable<T>,
predicate: (value: T, index: number, source: Observable<T>) => boolean,
resultSelector: void,
defaultValue?: T): Observable<T>;
/* tslint:enable:max-line-length */

/**
* Returns an Observable that emits only the last item emitted by the source Observable.
* It optionally takes a predicate function as a parameter, in which case, rather than emitting
Expand All @@ -33,15 +11,16 @@ export function last<T>(this: Observable<T>,
*
* @throws {EmptyError} Delivers an EmptyError to the Observer's `error`
* callback if the Observable completes before any `next` notification was sent.
* @param {function} predicate - The condition any source emitted item has to satisfy.
* @param {function} [predicate] - The condition any source emitted item has to satisfy.
* @param {any} [defaultValue] - The default value to use if the predicate isn't
* satisfied, or no values were emitted (if no predicate).
* @return {Observable} An Observable that emits only the last item satisfying the given condition
* from the source, or an NoSuchElementException if no such items are emitted.
* @throws - Throws if no items that match the predicate are emitted by the source Observable.
* @method last
* @owner Observable
*/
export function last<T, R>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean,
resultSelector?: ((value: T, index: number) => R) | void,
defaultValue?: R): Observable<T | R> {
return higherOrder(predicate, resultSelector as any, defaultValue)(this);
export function last<T>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean,
defaultValue?: T): Observable<T | T> {
return higherOrder(predicate, defaultValue)(this);
}

0 comments on commit 3240419

Please sign in to comment.