From 32404193b82b0f5f5e491d2de2bc9ef124292d2f Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Wed, 7 Feb 2018 16:05:49 -0800 Subject: [PATCH] feat(last): simplify interface - Removes resultSelector argument - Updates tests BREAKING CHANGE: no longer accepts `resultSelector` argument. To get this same functionality, use `map`. --- spec/operators/last-spec.ts | 46 +------------- src/internal/operators/last.ts | 83 +++++++------------------- src/internal/patching/operator/last.ts | 33 ++-------- 3 files changed, 31 insertions(+), 131 deletions(-) diff --git a/spec/operators/last-spec.ts b/spec/operators/last-spec.ts index a078eefba9..9e8562a641 100644 --- a/spec/operators/last-spec.ts +++ b/spec/operators/last-spec.ts @@ -1,4 +1,4 @@ -import { expect } from 'chai'; + import * as Rx from '../../src/Rx'; import { hot, cold, expectObservable, expectSubscriptions } from '../helpers/marble-testing'; @@ -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); }); @@ -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); }); @@ -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 @@ -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 diff --git a/src/internal/operators/last.ts b/src/internal/operators/last.ts index 964ce6e3d3..90a8de3c8a 100644 --- a/src/internal/operators/last.ts +++ b/src/internal/operators/last.ts @@ -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(predicate: (value: T, index: number, source: Observable) => value is S): OperatorFunction; -export function last(predicate: (value: T | S, index: number, source: Observable) => value is S, - resultSelector: (value: S, index: number) => R, defaultValue?: R): OperatorFunction; -export function last(predicate: (value: T, index: number, source: Observable) => value is S, - resultSelector: void, - defaultValue?: S): OperatorFunction; -export function last(predicate?: (value: T, index: number, source: Observable) => boolean): MonoTypeOperatorFunction; -export function last(predicate: (value: T, index: number, source: Observable) => boolean, - resultSelector?: (value: T, index: number) => R, - defaultValue?: R): OperatorFunction; -export function last(predicate: (value: T, index: number, source: Observable) => boolean, - resultSelector: void, - defaultValue?: T): MonoTypeOperatorFunction; -/* 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. @@ -30,28 +14,26 @@ export function last(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(predicate?: (value: T, index: number, source: Observable) => boolean, - resultSelector?: ((value: T, index: number) => R) | void, - defaultValue?: R): OperatorFunction { - return (source: Observable) => source.lift(new LastOperator(predicate, resultSelector, defaultValue, source)); +export function last(predicate?: (value: T, index: number, source: Observable) => boolean, + defaultValue?: T): MonoTypeOperatorFunction { + return (source: Observable) => source.lift(new LastOperator(predicate, defaultValue, source)); } -class LastOperator implements Operator { - constructor(private predicate?: (value: T, index: number, source: Observable) => boolean, - private resultSelector?: ((value: T, index: number) => R) | void, - private defaultValue?: any, - private source?: Observable) { +class LastOperator implements Operator { + constructor(private predicate: (value: T, index: number, source: Observable) => boolean, + private defaultValue: any, + private source: Observable) { } - call(observer: Subscriber, source: any): any { - return source.subscribe(new LastSubscriber(observer, this.predicate, this.resultSelector, this.defaultValue, this.source)); + call(observer: Subscriber, source: any): any { + return source.subscribe(new LastSubscriber(observer, this.predicate, this.defaultValue, this.source)); } } @@ -60,16 +42,15 @@ class LastOperator implements Operator { * @ignore * @extends {Ignored} */ -class LastSubscriber extends Subscriber { - private lastValue: T | R; - private hasValue: boolean = false; - private index: number = 0; +class LastSubscriber extends Subscriber { + private lastValue: T; + private hasValue = false; + private index = 0; - constructor(destination: Subscriber, - private predicate?: (value: T, index: number, source: Observable) => boolean, - private resultSelector?: ((value: T, index: number) => R) | void, - private defaultValue?: any, - private source?: Observable) { + constructor(destination: Subscriber, + private predicate: (value: T, index: number, source: Observable) => boolean, + private defaultValue: T, + private source: Observable) { super(destination); if (typeof defaultValue !== 'undefined') { this.lastValue = defaultValue; @@ -82,10 +63,6 @@ class LastSubscriber extends Subscriber { if (this.predicate) { this._tryPredicate(value, index); } else { - if (this.resultSelector) { - this._tryResultSelector(value, index); - return; - } this.lastValue = value; this.hasValue = true; } @@ -100,27 +77,11 @@ class LastSubscriber extends Subscriber { 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 = (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) { diff --git a/src/internal/patching/operator/last.ts b/src/internal/patching/operator/last.ts index a99af956b1..3770dd618c 100644 --- a/src/internal/patching/operator/last.ts +++ b/src/internal/patching/operator/last.ts @@ -1,28 +1,6 @@ import { Observable } from '../../Observable'; import { last as higherOrder } from '../../operators/last'; -/* tslint:disable:max-line-length */ -export function last(this: Observable, - predicate: (value: T, index: number, source: Observable) => value is S): Observable; -export function last(this: Observable, - predicate: (value: T | S, index: number, source: Observable) => value is S, - resultSelector: (value: S, index: number) => R, defaultValue?: R): Observable; -export function last(this: Observable, - predicate: (value: T, index: number, source: Observable) => value is S, - resultSelector: void, - defaultValue?: S): Observable; -export function last(this: Observable, - predicate?: (value: T, index: number, source: Observable) => boolean): Observable; -export function last(this: Observable, - predicate: (value: T, index: number, source: Observable) => boolean, - resultSelector?: (value: T, index: number) => R, - defaultValue?: R): Observable; -export function last(this: Observable, - predicate: (value: T, index: number, source: Observable) => boolean, - resultSelector: void, - defaultValue?: T): Observable; -/* 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 @@ -33,15 +11,16 @@ export function last(this: 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] - 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(this: Observable, predicate?: (value: T, index: number, source: Observable) => boolean, - resultSelector?: ((value: T, index: number) => R) | void, - defaultValue?: R): Observable { - return higherOrder(predicate, resultSelector as any, defaultValue)(this); +export function last(this: Observable, predicate?: (value: T, index: number, source: Observable) => boolean, + defaultValue?: T): Observable { + return higherOrder(predicate, defaultValue)(this); }