Skip to content

Commit

Permalink
feat(last): add resultSelector argument
Browse files Browse the repository at this point in the history
closes #418
  • Loading branch information
benlesh committed Oct 1, 2015
1 parent 3c20fcc commit 5a4896c
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 14 deletions.
35 changes: 33 additions & 2 deletions spec/operators/last-spec.js
Original file line number Diff line number Diff line change
@@ -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(){
Expand All @@ -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);
});
Expand All @@ -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);
});
});
2 changes: 1 addition & 1 deletion src/CoreOperators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface CoreOperators<T> {
groupBy?: <T, R>(keySelector: (value:T) => string, durationSelector?: (group:GroupSubject<R>) => Observable<any>, elementSelector?: (value:T) => R) => Observable<R>;
ignoreElements?: () => Observable<T>;
isEmpty?: () => Observable<boolean>;
last?: (predicate?: (value: T, index:number) => boolean, thisArg?: any, defaultValue?: any) => Observable<T>;
last?: <R>(predicate?: (value: T, index:number) => boolean, resultSelector?: (value: T, index: number) => R, thisArg?: any, defaultValue?: any) => Observable<T>;
map?: <T, R>(project: (x: T, ix?: number) => R, thisArg?: any) => Observable<R>;
mapTo?: <R>(value: R) => Observable<R>;
materialize?: () => Observable<any>;
Expand Down
37 changes: 26 additions & 11 deletions src/operators/last.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,30 @@ import {errorObject} from '../util/errorObject';
import bindCallback from '../util/bindCallback';
import EmptyError from '../util/EmptyError';

export default function last<T>(predicate?: (value: T, index: number, source:Observable<T>) => boolean, thisArg?: any, defaultValue?: any) : Observable<T> {
return this.lift(new LastOperator(predicate, thisArg, defaultValue, this));
export default function last<T, R>(predicate?: (value: T, index: number, source:Observable<T>) => boolean, resultSelector?: (value: T, index: number) => R, thisArg?: any, defaultValue?: any) : Observable<R> {
return this.lift(new LastOperator(predicate, resultSelector, thisArg, defaultValue, this));
}

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

}

call(observer: Subscriber<R>): Subscriber<T> {
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<T> extends Subscriber<T> {
class LastSubscriber<T, R> extends Subscriber<T> {
private lastValue: T;
private hasValue: boolean = false;
private predicate: Function;
private index: number = 0;

constructor(destination: Observer<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean,
private resultSelector?: (value: T, index: number) => R,
private thisArg?: any, private defaultValue?: any, private source?: Observable<T>) {
super(destination);
if(typeof defaultValue !== 'undefined') {
Expand All @@ -40,13 +43,25 @@ class LastSubscriber<T> extends Subscriber<T> {
}
}

_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;
}
Expand Down

0 comments on commit 5a4896c

Please sign in to comment.