Skip to content

Commit 3bc050a

Browse files
committed
feat(single): add higher-order lettable version of single
1 parent e8be197 commit 3bc050a

File tree

3 files changed

+98
-73
lines changed

3 files changed

+98
-73
lines changed

src/operator/single.ts

Lines changed: 2 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import { Observable } from '../Observable';
2-
import { Operator } from '../Operator';
3-
import { Subscriber } from '../Subscriber';
4-
import { Observer } from '../Observer';
5-
import { EmptyError } from '../util/EmptyError';
6-
import { TeardownLogic } from '../Subscription';
2+
import { single as higherOrder } from '../operators/single';
73

84
/**
95
* Returns an Observable that emits the single item emitted by the source Observable that matches a specified
@@ -22,72 +18,5 @@ import { TeardownLogic } from '../Subscription';
2218
* @owner Observable
2319
*/
2420
export function single<T>(this: Observable<T>, predicate?: (value: T, index: number, source: Observable<T>) => boolean): Observable<T> {
25-
return this.lift(new SingleOperator(predicate, this));
26-
}
27-
28-
class SingleOperator<T> implements Operator<T, T> {
29-
constructor(private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
30-
private source?: Observable<T>) {
31-
}
32-
33-
call(subscriber: Subscriber<T>, source: any): TeardownLogic {
34-
return source.subscribe(new SingleSubscriber(subscriber, this.predicate, this.source));
35-
}
36-
}
37-
38-
/**
39-
* We need this JSDoc comment for affecting ESDoc.
40-
* @ignore
41-
* @extends {Ignored}
42-
*/
43-
class SingleSubscriber<T> extends Subscriber<T> {
44-
private seenValue: boolean = false;
45-
private singleValue: T;
46-
private index: number = 0;
47-
48-
constructor(destination: Observer<T>,
49-
private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
50-
private source?: Observable<T>) {
51-
super(destination);
52-
}
53-
54-
private applySingleValue(value: T): void {
55-
if (this.seenValue) {
56-
this.destination.error('Sequence contains more than one element');
57-
} else {
58-
this.seenValue = true;
59-
this.singleValue = value;
60-
}
61-
}
62-
63-
protected _next(value: T): void {
64-
const index = this.index++;
65-
66-
if (this.predicate) {
67-
this.tryNext(value, index);
68-
} else {
69-
this.applySingleValue(value);
70-
}
71-
}
72-
73-
private tryNext(value: T, index: number): void {
74-
try {
75-
if (this.predicate(value, index, this.source)) {
76-
this.applySingleValue(value);
77-
}
78-
} catch (err) {
79-
this.destination.error(err);
80-
}
81-
}
82-
83-
protected _complete(): void {
84-
const destination = this.destination;
85-
86-
if (this.index > 0) {
87-
destination.next(this.seenValue ? this.singleValue : undefined);
88-
destination.complete();
89-
} else {
90-
destination.error(new EmptyError);
91-
}
92-
}
21+
return higherOrder(predicate)(this);
9322
}

src/operators/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export { scan } from './scan';
6464
export { sequenceEqual } from './sequenceEqual';
6565
export { share } from './share';
6666
export { shareReplay } from './shareReplay';
67+
export { single } from './single';
6768
export { subscribeOn } from './subscribeOn';
6869
export { switchAll } from './switchAll';
6970
export { switchMap } from './switchMap';

src/operators/single.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { Observable } from '../Observable';
2+
import { Operator } from '../Operator';
3+
import { Subscriber } from '../Subscriber';
4+
import { Observer } from '../Observer';
5+
import { EmptyError } from '../util/EmptyError';
6+
import { TeardownLogic } from '../Subscription';
7+
8+
import { MonoTypeOperatorFunction } from '../interfaces';
9+
10+
/**
11+
* Returns an Observable that emits the single item emitted by the source Observable that matches a specified
12+
* predicate, if that Observable emits one such item. If the source Observable emits more than one such item or no
13+
* such items, notify of an IllegalArgumentException or NoSuchElementException respectively.
14+
*
15+
* <img src="./img/single.png" width="100%">
16+
*
17+
* @throws {EmptyError} Delivers an EmptyError to the Observer's `error`
18+
* callback if the Observable completes before any `next` notification was sent.
19+
* @param {Function} predicate - A predicate function to evaluate items emitted by the source Observable.
20+
* @return {Observable<T>} An Observable that emits the single item emitted by the source Observable that matches
21+
* the predicate.
22+
.
23+
* @method single
24+
* @owner Observable
25+
*/
26+
export function single<T>(predicate?: (value: T, index: number, source: Observable<T>) => boolean): MonoTypeOperatorFunction<T> {
27+
return (source: Observable<T>) => source.lift(new SingleOperator(predicate, source));
28+
}
29+
30+
class SingleOperator<T> implements Operator<T, T> {
31+
constructor(private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
32+
private source?: Observable<T>) {
33+
}
34+
35+
call(subscriber: Subscriber<T>, source: any): TeardownLogic {
36+
return source.subscribe(new SingleSubscriber(subscriber, this.predicate, this.source));
37+
}
38+
}
39+
40+
/**
41+
* We need this JSDoc comment for affecting ESDoc.
42+
* @ignore
43+
* @extends {Ignored}
44+
*/
45+
class SingleSubscriber<T> extends Subscriber<T> {
46+
private seenValue: boolean = false;
47+
private singleValue: T;
48+
private index: number = 0;
49+
50+
constructor(destination: Observer<T>,
51+
private predicate?: (value: T, index: number, source: Observable<T>) => boolean,
52+
private source?: Observable<T>) {
53+
super(destination);
54+
}
55+
56+
private applySingleValue(value: T): void {
57+
if (this.seenValue) {
58+
this.destination.error('Sequence contains more than one element');
59+
} else {
60+
this.seenValue = true;
61+
this.singleValue = value;
62+
}
63+
}
64+
65+
protected _next(value: T): void {
66+
const index = this.index++;
67+
68+
if (this.predicate) {
69+
this.tryNext(value, index);
70+
} else {
71+
this.applySingleValue(value);
72+
}
73+
}
74+
75+
private tryNext(value: T, index: number): void {
76+
try {
77+
if (this.predicate(value, index, this.source)) {
78+
this.applySingleValue(value);
79+
}
80+
} catch (err) {
81+
this.destination.error(err);
82+
}
83+
}
84+
85+
protected _complete(): void {
86+
const destination = this.destination;
87+
88+
if (this.index > 0) {
89+
destination.next(this.seenValue ? this.singleValue : undefined);
90+
destination.complete();
91+
} else {
92+
destination.error(new EmptyError);
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)