Skip to content

Commit bf33b97

Browse files
committed
feat(last): add higher-order lettable version of last
1 parent 3136403 commit bf33b97

File tree

3 files changed

+136
-93
lines changed

3 files changed

+136
-93
lines changed

src/operator/last.ts

Lines changed: 2 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { Observable } from '../Observable';
2-
import { Operator } from '../Operator';
3-
import { Subscriber } from '../Subscriber';
4-
import { EmptyError } from '../util/EmptyError';
2+
import { last as higherOrder } from '../operators';
53

64
/* tslint:disable:max-line-length */
75
export function last<T, S extends T>(this: Observable<T>,
@@ -45,94 +43,5 @@ export function last<T>(this: Observable<T>,
4543
export function last<T, R>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean,
4644
resultSelector?: ((value: T, index: number) => R) | void,
4745
defaultValue?: R): Observable<T | R> {
48-
return this.lift(new LastOperator(predicate, resultSelector, defaultValue, this));
49-
}
50-
51-
class LastOperator<T, R> implements Operator<T, R> {
52-
constructor(private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
53-
private resultSelector?: ((value: T, index: number) => R) | void,
54-
private defaultValue?: any,
55-
private source?: Observable<T>) {
56-
}
57-
58-
call(observer: Subscriber<R>, source: any): any {
59-
return source.subscribe(new LastSubscriber(observer, this.predicate, this.resultSelector, this.defaultValue, this.source));
60-
}
61-
}
62-
63-
/**
64-
* We need this JSDoc comment for affecting ESDoc.
65-
* @ignore
66-
* @extends {Ignored}
67-
*/
68-
class LastSubscriber<T, R> extends Subscriber<T> {
69-
private lastValue: T | R;
70-
private hasValue: boolean = false;
71-
private index: number = 0;
72-
73-
constructor(destination: Subscriber<R>,
74-
private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
75-
private resultSelector?: ((value: T, index: number) => R) | void,
76-
private defaultValue?: any,
77-
private source?: Observable<T>) {
78-
super(destination);
79-
if (typeof defaultValue !== 'undefined') {
80-
this.lastValue = defaultValue;
81-
this.hasValue = true;
82-
}
83-
}
84-
85-
protected _next(value: T): void {
86-
const index = this.index++;
87-
if (this.predicate) {
88-
this._tryPredicate(value, index);
89-
} else {
90-
if (this.resultSelector) {
91-
this._tryResultSelector(value, index);
92-
return;
93-
}
94-
this.lastValue = value;
95-
this.hasValue = true;
96-
}
97-
}
98-
99-
private _tryPredicate(value: T, index: number) {
100-
let result: any;
101-
try {
102-
result = this.predicate(value, index, this.source);
103-
} catch (err) {
104-
this.destination.error(err);
105-
return;
106-
}
107-
if (result) {
108-
if (this.resultSelector) {
109-
this._tryResultSelector(value, index);
110-
return;
111-
}
112-
this.lastValue = value;
113-
this.hasValue = true;
114-
}
115-
}
116-
117-
private _tryResultSelector(value: T, index: number) {
118-
let result: any;
119-
try {
120-
result = (<any>this).resultSelector(value, index);
121-
} catch (err) {
122-
this.destination.error(err);
123-
return;
124-
}
125-
this.lastValue = result;
126-
this.hasValue = true;
127-
}
128-
129-
protected _complete(): void {
130-
const destination = this.destination;
131-
if (this.hasValue) {
132-
destination.next(this.lastValue);
133-
destination.complete();
134-
} else {
135-
destination.error(new EmptyError);
136-
}
137-
}
46+
return higherOrder(predicate, resultSelector as any, defaultValue)(this);
13847
}

src/operators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export { first } from './first';
3232
export { groupBy } from './groupBy';
3333
export { ignoreElements } from './ignoreElements';
3434
export { isEmpty } from './isEmpty';
35+
export { last } from './last';
3536
export { map } from './map';
3637
export { materialize } from './materialize';
3738
export { max } from './max';

src/operators/last.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import { Observable } from '../Observable';
2+
import { Operator } from '../Operator';
3+
import { Subscriber } from '../Subscriber';
4+
import { EmptyError } from '../util/EmptyError';
5+
import { OperatorFunction, MonoTypeOperatorFunction } from '../interfaces';
6+
7+
/* tslint:disable:max-line-length */
8+
export function last<T, S extends T>(predicate: (value: T, index: number, source: Observable<T>) => value is S): OperatorFunction<T, S>;
9+
export function last<T, S extends T, R>(predicate: (value: T | S, index: number, source: Observable<T>) => value is S,
10+
resultSelector: (value: S, index: number) => R, defaultValue?: R): OperatorFunction<T, R>;
11+
export function last<T, S extends T>(predicate: (value: T, index: number, source: Observable<T>) => value is S,
12+
resultSelector: void,
13+
defaultValue?: S): OperatorFunction<T, S>;
14+
export function last<T>(predicate?: (value: T, index: number, source: Observable<T>) => boolean): MonoTypeOperatorFunction<T>;
15+
export function last<T, R>(predicate: (value: T, index: number, source: Observable<T>) => boolean,
16+
resultSelector?: (value: T, index: number) => R,
17+
defaultValue?: R): OperatorFunction<T, R>;
18+
export function last<T>(predicate: (value: T, index: number, source: Observable<T>) => boolean,
19+
resultSelector: void,
20+
defaultValue?: T): MonoTypeOperatorFunction<T>;
21+
/* tslint:enable:max-line-length */
22+
23+
/**
24+
* Returns an Observable that emits only the last item emitted by the source Observable.
25+
* It optionally takes a predicate function as a parameter, in which case, rather than emitting
26+
* the last item from the source Observable, the resulting Observable will emit the last item
27+
* from the source Observable that satisfies the predicate.
28+
*
29+
* <img src="./img/last.png" width="100%">
30+
*
31+
* @throws {EmptyError} Delivers an EmptyError to the Observer's `error`
32+
* callback if the Observable completes before any `next` notification was sent.
33+
* @param {function} predicate - The condition any source emitted item has to satisfy.
34+
* @return {Observable} An Observable that emits only the last item satisfying the given condition
35+
* from the source, or an NoSuchElementException if no such items are emitted.
36+
* @throws - Throws if no items that match the predicate are emitted by the source Observable.
37+
* @method last
38+
* @owner Observable
39+
*/
40+
export function last<T, R>(predicate?: (value: T, index: number, source: Observable<T>) => boolean,
41+
resultSelector?: ((value: T, index: number) => R) | void,
42+
defaultValue?: R): OperatorFunction<T, T | R> {
43+
return (source: Observable<T>) => source.lift(new LastOperator(predicate, resultSelector, defaultValue, source));
44+
}
45+
46+
class LastOperator<T, R> implements Operator<T, R> {
47+
constructor(private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
48+
private resultSelector?: ((value: T, index: number) => R) | void,
49+
private defaultValue?: any,
50+
private source?: Observable<T>) {
51+
}
52+
53+
call(observer: Subscriber<R>, source: any): any {
54+
return source.subscribe(new LastSubscriber(observer, this.predicate, this.resultSelector, this.defaultValue, this.source));
55+
}
56+
}
57+
58+
/**
59+
* We need this JSDoc comment for affecting ESDoc.
60+
* @ignore
61+
* @extends {Ignored}
62+
*/
63+
class LastSubscriber<T, R> extends Subscriber<T> {
64+
private lastValue: T | R;
65+
private hasValue: boolean = false;
66+
private index: number = 0;
67+
68+
constructor(destination: Subscriber<R>,
69+
private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
70+
private resultSelector?: ((value: T, index: number) => R) | void,
71+
private defaultValue?: any,
72+
private source?: Observable<T>) {
73+
super(destination);
74+
if (typeof defaultValue !== 'undefined') {
75+
this.lastValue = defaultValue;
76+
this.hasValue = true;
77+
}
78+
}
79+
80+
protected _next(value: T): void {
81+
const index = this.index++;
82+
if (this.predicate) {
83+
this._tryPredicate(value, index);
84+
} else {
85+
if (this.resultSelector) {
86+
this._tryResultSelector(value, index);
87+
return;
88+
}
89+
this.lastValue = value;
90+
this.hasValue = true;
91+
}
92+
}
93+
94+
private _tryPredicate(value: T, index: number) {
95+
let result: any;
96+
try {
97+
result = this.predicate(value, index, this.source);
98+
} catch (err) {
99+
this.destination.error(err);
100+
return;
101+
}
102+
if (result) {
103+
if (this.resultSelector) {
104+
this._tryResultSelector(value, index);
105+
return;
106+
}
107+
this.lastValue = value;
108+
this.hasValue = true;
109+
}
110+
}
111+
112+
private _tryResultSelector(value: T, index: number) {
113+
let result: any;
114+
try {
115+
result = (<any>this).resultSelector(value, index);
116+
} catch (err) {
117+
this.destination.error(err);
118+
return;
119+
}
120+
this.lastValue = result;
121+
this.hasValue = true;
122+
}
123+
124+
protected _complete(): void {
125+
const destination = this.destination;
126+
if (this.hasValue) {
127+
destination.next(this.lastValue);
128+
destination.complete();
129+
} else {
130+
destination.error(new EmptyError);
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)