Skip to content

Commit c4f44b9

Browse files
arpadvasbenlesh
authored andcommittedMar 15, 2019
fix(throwIfEmpty): ensure result is retry-able
* feat(tap): reset throwIfEmpty hasValue on retry * refactor(throwIfEmpty): re-implement throwIfEmpty * refactor(throwIfEmpty): fix typos * refactor(throwIfEmpty): remove tap changes * refactor(throwIfEmpty): fix type errors * refactor(throwIfEmpty): update typing info and complete method
1 parent f6acdc0 commit c4f44b9

File tree

2 files changed

+112
-14
lines changed

2 files changed

+112
-14
lines changed
 

‎spec/operators/throwIfEmpty-spec.ts

+67-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { expect } from 'chai';
22
import { hot, cold, expectObservable, expectSubscriptions } from '../helpers/marble-testing';
3-
import { EMPTY, of, EmptyError } from 'rxjs';
4-
import { throwIfEmpty } from 'rxjs/operators';
3+
import { EMPTY, of, EmptyError, defer, throwError } from 'rxjs';
4+
import { throwIfEmpty, mergeMap, retry } from 'rxjs/operators';
55

66
declare function asDiagram(arg: string): Function;
77

@@ -77,6 +77,39 @@ describe('throwIfEmpty', () => {
7777
).toBe(expected, undefined, new Error('test'));
7878
expectSubscriptions(source.subscriptions).toBe([sub1]);
7979
});
80+
81+
it('should throw if empty after retry', () => {
82+
const error = new Error('So empty inside');
83+
let thrown: any;
84+
let sourceIsEmpty = false;
85+
86+
const source = defer(() => {
87+
if (sourceIsEmpty) {
88+
return EMPTY;
89+
}
90+
sourceIsEmpty = true;
91+
return of(1, 2);
92+
});
93+
94+
source.pipe(
95+
throwIfEmpty(() => error),
96+
mergeMap(value => {
97+
if (value > 1) {
98+
return throwError(new Error());
99+
}
100+
101+
return of(value);
102+
}),
103+
retry(1)
104+
).subscribe({
105+
error(err) {
106+
thrown = err;
107+
}
108+
});
109+
110+
expect(thrown).to.equal(error);
111+
112+
});
80113
});
81114

82115
describe('without errorFactory', () => {
@@ -139,5 +172,37 @@ describe('throwIfEmpty', () => {
139172
).toBe(expected, undefined, new EmptyError());
140173
expectSubscriptions(source.subscriptions).toBe([sub1]);
141174
});
175+
176+
it('should throw if empty after retry', () => {
177+
let thrown: any;
178+
let sourceIsEmpty = false;
179+
180+
const source = defer(() => {
181+
if (sourceIsEmpty) {
182+
return EMPTY;
183+
}
184+
sourceIsEmpty = true;
185+
return of(1, 2);
186+
});
187+
188+
source.pipe(
189+
throwIfEmpty(),
190+
mergeMap(value => {
191+
if (value > 1) {
192+
return throwError(new Error());
193+
}
194+
195+
return of(value);
196+
}),
197+
retry(1)
198+
).subscribe({
199+
error(err) {
200+
thrown = err;
201+
}
202+
});
203+
204+
expect(thrown).to.be.instanceof(EmptyError);
205+
206+
});
142207
});
143208
});

‎src/internal/operators/throwIfEmpty.ts

+45-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { tap } from './tap';
21
import { EmptyError } from '../util/EmptyError';
3-
import { MonoTypeOperatorFunction } from '../types';
2+
import { Observable } from '../Observable';
3+
import { Operator } from '../Operator';
4+
import { Subscriber } from '../Subscriber';
5+
import { TeardownLogic, MonoTypeOperatorFunction } from '../types';
46

57
/**
68
* If the source observable completes without emitting a value, it will emit
@@ -24,24 +26,55 @@ import { MonoTypeOperatorFunction } from '../types';
2426
* )
2527
* .subscribe({
2628
* next() { console.log('The button was clicked'); },
27-
* error(err) { console.error(err); },
29+
* error(err) { console.error(err); }
2830
* });
2931
* ```
3032
*
31-
* @param {Function} [errorFactory] A factory function called to produce the
33+
* @param errorFactory A factory function called to produce the
3234
* error to be thrown when the source observable completes without emitting a
3335
* value.
3436
*/
35-
export const throwIfEmpty =
36-
<T>(errorFactory: (() => any) = defaultErrorFactory) => tap<T>({
37-
hasValue: false,
38-
next() { this.hasValue = true; },
39-
complete() {
40-
if (!this.hasValue) {
41-
throw errorFactory();
37+
export function throwIfEmpty <T>(errorFactory: (() => any) = defaultErrorFactory): MonoTypeOperatorFunction<T> {
38+
return (source: Observable<T>) => {
39+
return source.lift(new ThrowIfEmptyOperator(errorFactory));
40+
};
41+
}
42+
43+
class ThrowIfEmptyOperator<T> implements Operator<T, T> {
44+
constructor(private errorFactory: () => any) {
45+
}
46+
47+
call(subscriber: Subscriber<T>, source: any): TeardownLogic {
48+
return source.subscribe(new ThrowIfEmptySubscriber(subscriber, this.errorFactory));
49+
}
50+
}
51+
52+
class ThrowIfEmptySubscriber<T> extends Subscriber<T> {
53+
private hasValue: boolean = false;
54+
55+
constructor(destination: Subscriber<T>, private errorFactory: () => any) {
56+
super(destination);
57+
}
58+
59+
protected _next(value: T): void {
60+
this.hasValue = true;
61+
this.destination.next(value);
62+
}
63+
64+
protected _complete() {
65+
if (!this.hasValue) {
66+
let err: any;
67+
try {
68+
err = this.errorFactory();
69+
} catch (e) {
70+
err = e;
4271
}
72+
this.destination.error(err);
73+
} else {
74+
return this.destination.complete();
4375
}
44-
} as any);
76+
}
77+
}
4578

4679
function defaultErrorFactory() {
4780
return new EmptyError();

0 commit comments

Comments
 (0)
Please sign in to comment.