diff --git a/spec/operators/last-spec.js b/spec/operators/last-spec.js index f28c787b61..2fc0c759a6 100644 --- a/spec/operators/last-spec.js +++ b/spec/operators/last-spec.js @@ -1,5 +1,6 @@ /* globals describe, it, expect, expectObservable, hot, cold */ var Rx = require('../../dist/cjs/Rx'); +var Observable = Rx.Observable; describe('Observable.prototype.last()', function(){ it('should take the last value of an observable', function(){ @@ -8,14 +9,20 @@ describe('Observable.prototype.last()', function(){ expectObservable(e1.last()).toBe(expected) }); - it('should error on empty', function() { + it('should error on nothing sent but completed', function() { var e1 = hot('--a--^----|'); var expected = '-----#'; expectObservable(e1.last()).toBe(expected, null, new Rx.EmptyError()); }); + it('should error on empty', function (){ + var e1 = Observable.empty() + var expected = '#'; + expectObservable(e1.last()).toBe(expected, null, new Rx.EmptyError()); + }); + it('should go on forever on never', function() { - var e2 = hot('--^---'); + var e2 = Observable.never(); var expected = '----'; expectObservable(e2.last()).toBe(expected); }); @@ -30,4 +37,28 @@ describe('Observable.prototype.last()', function(){ expectObservable(e1.last(predicate)).toBe(expected); }); + + it('should return a default value if no element found', function() { + var e1 = Observable.empty(); + var expected = '(a|)'; + expectObservable(e1.last(null, null, null, 'a')).toBe(expected); + }); + + it('should not return default value if an element is found', function (){ + var e1 = hot('--a---^---b---c---d---|'); + var expected = '----------------(d|)'; + expectObservable(e1.last(null, null, null, 'x')).toBe(expected); + }); + + it('should support a result selector argument', function() { + var e1 = hot('--a--^---b---c---d---e--|'); + var expected = '-------------------(x|)'; + var predicate = function (x){ return x === 'c'; }; + var resultSelector = function(x, i) { + expect(i).toBe(1); + expect(x).toBe('c'); + return 'x'; + }; + expectObservable(e1.last(predicate, resultSelector)).toBe(expected); + }); }); \ No newline at end of file diff --git a/src/CoreOperators.ts b/src/CoreOperators.ts index 574a1cfc00..52b605103b 100644 --- a/src/CoreOperators.ts +++ b/src/CoreOperators.ts @@ -32,7 +32,7 @@ export interface CoreOperators { groupBy?: (keySelector: (value:T) => string, durationSelector?: (group:GroupSubject) => Observable, elementSelector?: (value:T) => R) => Observable; ignoreElements?: () => Observable; isEmpty?: () => Observable; - last?: (predicate?: (value: T, index:number) => boolean, thisArg?: any, defaultValue?: any) => Observable; + last?: (predicate?: (value: T, index:number) => boolean, resultSelector?: (value: T, index: number) => R, thisArg?: any, defaultValue?: any) => Observable; map?: (project: (x: T, ix?: number) => R, thisArg?: any) => Observable; mapTo?: (value: R) => Observable; materialize?: () => Observable; diff --git a/src/operators/last.ts b/src/operators/last.ts index 51282c9047..c75d6891e3 100644 --- a/src/operators/last.ts +++ b/src/operators/last.ts @@ -8,27 +8,30 @@ import {errorObject} from '../util/errorObject'; import bindCallback from '../util/bindCallback'; import EmptyError from '../util/EmptyError'; -export default function last(predicate?: (value: T, index: number, source:Observable) => boolean, thisArg?: any, defaultValue?: any) : Observable { - return this.lift(new LastOperator(predicate, thisArg, defaultValue, this)); +export default function last(predicate?: (value: T, index: number, source:Observable) => boolean, resultSelector?: (value: T, index: number) => R, thisArg?: any, defaultValue?: any) : Observable { + return this.lift(new LastOperator(predicate, resultSelector, thisArg, defaultValue, this)); } class LastOperator implements Operator { - constructor(private predicate?: (value: T, index: number, source:Observable) => boolean, private thisArg?: any, private defaultValue?: any, private source?: Observable) { + constructor(private predicate?: (value: T, index: number, source:Observable) => boolean, + private resultSelector?: (value: T, index: number) => R, + private thisArg?: any, private defaultValue?: any, private source?: Observable) { } call(observer: Subscriber): Subscriber { - return new LastSubscriber(observer, this.predicate, this.thisArg, this.defaultValue, this.source); + return new LastSubscriber(observer, this.predicate, this.resultSelector, this.thisArg, this.defaultValue, this.source); } } -class LastSubscriber extends Subscriber { +class LastSubscriber extends Subscriber { private lastValue: T; private hasValue: boolean = false; private predicate: Function; private index: number = 0; constructor(destination: Observer, predicate?: (value: T, index: number, source: Observable) => boolean, + private resultSelector?: (value: T, index: number) => R, private thisArg?: any, private defaultValue?: any, private source?: Observable) { super(destination); if(typeof defaultValue !== 'undefined') { @@ -40,13 +43,25 @@ class LastSubscriber extends Subscriber { } } - _next(value: T) { - const predicate = this.predicate; + _next(value: any) { + const { predicate, resultSelector, destination } = this; + const index = this.index++; + if(predicate) { - let result = tryCatch(predicate)(value, this.index++, this.source); - if(result === errorObject) { - this.destination.error(result.e); - } else if (result) { + let found = tryCatch(predicate)(value, index, this.source); + if(found === errorObject) { + destination.error(errorObject.e); + return; + } + + if(found) { + if(resultSelector) { + value = tryCatch(resultSelector)(value, index); + if(value === errorObject) { + destination.error(errorObject.e); + return; + } + } this.lastValue = value; this.hasValue = true; }