From 5c7c7495c52c9cb0ca86476780cd1bfcdd9c23f7 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Mon, 22 Jan 2018 13:17:31 -0800 Subject: [PATCH] feat(empty): empty() returns the same instance - Adds more robust tests - Ensures that all calls to empty without a scheduler return a single instance - Removes EmptyObservable - Updates other tests and code using EmptyObservable EmptyObservable BREAKING CHANGE: `empty()` without a scheduler will return the same instance every time. BREAKING CHANGE: In TypeScript, `empty()` no longer accepts a generic argument, as it returns `Observable` --- spec/observables/empty-spec.ts | 38 +++++++-- spec/observables/of-spec.ts | 12 +-- src/internal/Notification.ts | 2 +- src/internal/observable/EmptyObservable.ts | 83 ------------------- src/internal/observable/ForkJoinObservable.ts | 6 +- src/internal/observable/empty.ts | 56 ++++++++++++- src/internal/operators/repeat.ts | 4 +- src/internal/operators/startWith.ts | 4 +- src/internal/operators/take.ts | 4 +- src/internal/operators/takeLast.ts | 4 +- 10 files changed, 101 insertions(+), 112 deletions(-) delete mode 100644 src/internal/observable/EmptyObservable.ts diff --git a/spec/observables/empty-spec.ts b/spec/observables/empty-spec.ts index 311b5a675f..6cfc34a726 100644 --- a/spec/observables/empty-spec.ts +++ b/spec/observables/empty-spec.ts @@ -1,16 +1,42 @@ -import * as Rx from '../../src/Rx'; import marbleTestingSignature = require('../helpers/marble-testing'); // tslint:disable-line:no-require-imports +import { empty } from '../../src/create'; +import { expect } from 'chai'; -declare const { asDiagram }; +declare const asDiagram: any; declare const expectObservable: typeof marbleTestingSignature.expectObservable; - -const Observable = Rx.Observable; +declare const rxTestScheduler: any; /** @test {empty} */ -describe('Observable.empty', () => { +describe('empty', () => { asDiagram('empty')('should create a cold observable with only complete', () => { const expected = '|'; - const e1 = Observable.empty(); + const e1 = empty(); expectObservable(e1).toBe(expected); }); + + it('should return the same instance EMPTY', () => { + const s1 = empty(); + const s2 = empty(); + expect(s1).to.equal(s2); + }); + + it('should be synchronous by default', () => { + const source = empty(); + let hit = false; + source.subscribe({ + complete() { hit = true; } + }); + expect(hit).to.be.true; + }); + + it('should take a scheduler', () => { + const source = empty(rxTestScheduler); + let hit = false; + source.subscribe({ + complete() { hit = true; } + }); + expect(hit).to.be.false; + rxTestScheduler.flush(); + expect(hit).to.be.true; + }); }); diff --git a/spec/observables/of-spec.ts b/spec/observables/of-spec.ts index 68a915f351..a0a78a1fb1 100644 --- a/spec/observables/of-spec.ts +++ b/spec/observables/of-spec.ts @@ -1,9 +1,8 @@ import { expect } from 'chai'; import * as Rx from '../../src/Rx'; -import { EmptyObservable } from '../../src/internal/observable/EmptyObservable'; +import { empty } from '../../src/internal/observable/empty'; import marbleTestingSignature = require('../helpers/marble-testing'); // tslint:disable-line:no-require-imports - -declare const { asDiagram }; +declare const asDiagram: any; declare const expectObservable: typeof marbleTestingSignature.expectObservable; declare const rxTestScheduler: Rx.TestScheduler; const Observable = Rx.Observable; @@ -35,12 +34,7 @@ describe('Observable.of', () => { it('should return an empty observable if passed no values', () => { const obs = Observable.of(); - expect(obs instanceof EmptyObservable).to.be.true; - }); - - it('should return an empty observable if passed only a scheduler', () => { - const obs = Observable.of(Rx.Scheduler.queue); - expect(obs instanceof EmptyObservable).to.be.true; + expect(obs).to.equal(empty()); }); it('should emit one value', (done: MochaDone) => { diff --git a/src/internal/Notification.ts b/src/internal/Notification.ts index 14ddda47bb..ae8857243e 100644 --- a/src/internal/Notification.ts +++ b/src/internal/Notification.ts @@ -91,7 +91,7 @@ export class Notification { case 'E': return _throw(this.error); case 'C': - return empty(); + return empty(); } throw new Error('unexpected notification kind value'); } diff --git a/src/internal/observable/EmptyObservable.ts b/src/internal/observable/EmptyObservable.ts deleted file mode 100644 index 2d819c6b52..0000000000 --- a/src/internal/observable/EmptyObservable.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { IScheduler } from '../Scheduler'; -import { Subscriber } from '../Subscriber'; -import { Observable } from '../Observable'; -import { TeardownLogic } from '../Subscription'; - -export interface DispatchArg { - subscriber: Subscriber; -} - -/** - * We need this JSDoc comment for affecting ESDoc. - * @extends {Ignored} - * @hide true - */ -export class EmptyObservable extends Observable { - - /** - * Creates an Observable that emits no items to the Observer and immediately - * emits a complete notification. - * - * Just emits 'complete', and nothing else. - * - * - * - * - * This static operator is useful for creating a simple Observable that only - * emits the complete notification. It can be used for composing with other - * Observables, such as in a {@link mergeMap}. - * - * @example Emit the number 7, then complete. - * var result = Rx.Observable.empty().startWith(7); - * result.subscribe(x => console.log(x)); - * - * @example Map and flatten only odd numbers to the sequence 'a', 'b', 'c' - * var interval = Rx.Observable.interval(1000); - * var result = interval.mergeMap(x => - * x % 2 === 1 ? Rx.Observable.of('a', 'b', 'c') : Rx.Observable.empty() - * ); - * result.subscribe(x => console.log(x)); - * - * // Results in the following to the console: - * // x is equal to the count on the interval eg(0,1,2,3,...) - * // x will occur every 1000ms - * // if x % 2 is equal to 1 print abc - * // if x % 2 is not equal to 1 nothing will be output - * - * @see {@link create} - * @see {@link never} - * @see {@link of} - * @see {@link throw} - * - * @param {Scheduler} [scheduler] A {@link IScheduler} to use for scheduling - * the emission of the complete notification. - * @return {Observable} An "empty" Observable: emits only the complete - * notification. - * @static true - * @name empty - * @owner Observable - */ - static create(scheduler?: IScheduler): Observable { - return new EmptyObservable(scheduler); - } - - static dispatch(arg: DispatchArg) { - const { subscriber } = arg; - subscriber.complete(); - } - - constructor(private scheduler?: IScheduler) { - super(); - } - - protected _subscribe(subscriber: Subscriber): TeardownLogic { - - const scheduler = this.scheduler; - - if (scheduler) { - return scheduler.schedule(EmptyObservable.dispatch, 0, { subscriber }); - } else { - subscriber.complete(); - } - } -} diff --git a/src/internal/observable/ForkJoinObservable.ts b/src/internal/observable/ForkJoinObservable.ts index aaaaa3dde3..4c4fd7d98f 100644 --- a/src/internal/observable/ForkJoinObservable.ts +++ b/src/internal/observable/ForkJoinObservable.ts @@ -1,7 +1,7 @@ import { Observable, SubscribableOrPromise } from '../Observable'; import { Subscriber } from '../Subscriber'; import { Subscription } from '../Subscription'; -import { EmptyObservable } from './EmptyObservable'; +import { empty } from './empty'; import { isArray } from '..//util/isArray'; import { subscribeToResult } from '..//util/subscribeToResult'; @@ -140,7 +140,7 @@ export class ForkJoinObservable extends Observable { Array> | ((...values: Array) => any)>): Observable { if (sources === null || arguments.length === 0) { - return new EmptyObservable(); + return empty(); } let resultSelector: (...values: Array) => any = null; @@ -155,7 +155,7 @@ export class ForkJoinObservable extends Observable { } if (sources.length === 0) { - return new EmptyObservable(); + return empty(); } return new ForkJoinObservable(>>sources, resultSelector); diff --git a/src/internal/observable/empty.ts b/src/internal/observable/empty.ts index 06d5615052..b3ace51c20 100644 --- a/src/internal/observable/empty.ts +++ b/src/internal/observable/empty.ts @@ -1,3 +1,55 @@ -import { EmptyObservable } from './EmptyObservable'; +import { Observable } from '../Observable'; +import { IScheduler } from '../Scheduler'; -export const empty = EmptyObservable.create; \ No newline at end of file +export const EMPTY = new Observable(subscriber => subscriber.complete()); + +/** + * Creates an Observable that emits no items to the Observer and immediately + * emits a complete notification. + * + * Just emits 'complete', and nothing else. + * + * + * + * + * This static operator is useful for creating a simple Observable that only + * emits the complete notification. It can be used for composing with other + * Observables, such as in a {@link mergeMap}. + * + * @example Emit the number 7, then complete. + * var result = Rx.Observable.empty().startWith(7); + * result.subscribe(x => console.log(x)); + * + * @example Map and flatten only odd numbers to the sequence 'a', 'b', 'c' + * var interval = Rx.Observable.interval(1000); + * var result = interval.mergeMap(x => + * x % 2 === 1 ? Rx.Observable.of('a', 'b', 'c') : Rx.Observable.empty() + * ); + * result.subscribe(x => console.log(x)); + * + * // Results in the following to the console: + * // x is equal to the count on the interval eg(0,1,2,3,...) + * // x will occur every 1000ms + * // if x % 2 is equal to 1 print abc + * // if x % 2 is not equal to 1 nothing will be output + * + * @see {@link create} + * @see {@link never} + * @see {@link of} + * @see {@link throw} + * + * @param {Scheduler} [scheduler] A {@link IScheduler} to use for scheduling + * the emission of the complete notification. + * @return {Observable} An "empty" Observable: emits only the complete + * notification. + * @static true + * @name empty + * @owner Observable + */ +export function empty(scheduler?: IScheduler) { + return scheduler ? emptyScheduled(scheduler) : EMPTY; +} + +export function emptyScheduled(scheduler: IScheduler) { + return new Observable(subscriber => scheduler.schedule(() => subscriber.complete())); +} diff --git a/src/internal/operators/repeat.ts b/src/internal/operators/repeat.ts index e47c002d70..9b5ca35e1a 100644 --- a/src/internal/operators/repeat.ts +++ b/src/internal/operators/repeat.ts @@ -1,7 +1,7 @@ import { Operator } from '../Operator'; import { Subscriber } from '../Subscriber'; import { Observable } from '../Observable'; -import { EmptyObservable } from '../observable/EmptyObservable'; +import { empty } from '../observable/empty'; import { TeardownLogic } from '../Subscription'; import { MonoTypeOperatorFunction } from '../../internal/types'; @@ -20,7 +20,7 @@ import { MonoTypeOperatorFunction } from '../../internal/types'; export function repeat(count: number = -1): MonoTypeOperatorFunction { return (source: Observable) => { if (count === 0) { - return new EmptyObservable(); + return empty(); } else if (count < 0) { return source.lift(new RepeatOperator(-1, source)); } else { diff --git a/src/internal/operators/startWith.ts b/src/internal/operators/startWith.ts index 5abf2f2f8d..8b7b0011bb 100644 --- a/src/internal/operators/startWith.ts +++ b/src/internal/operators/startWith.ts @@ -2,7 +2,7 @@ import { IScheduler } from '../Scheduler'; import { Observable } from '../Observable'; import { fromArray } from '../observable/fromArray'; import { scalar } from '../observable/scalar'; -import { EmptyObservable } from '../observable/EmptyObservable'; +import { empty } from '../observable/empty'; import { concat as concatStatic } from '../observable/concat'; import { isScheduler } from '..//util/isScheduler'; import { MonoTypeOperatorFunction } from '../../internal/types'; @@ -46,7 +46,7 @@ export function startWith(...array: Array): MonoTypeOperatorF } else if (len > 0) { return concatStatic(fromArray(array as T[], scheduler), source); } else { - return concatStatic(new EmptyObservable(scheduler), source); + return concatStatic(empty(scheduler), source); } }; } diff --git a/src/internal/operators/take.ts b/src/internal/operators/take.ts index 1ea17daa8b..842e6a68f3 100644 --- a/src/internal/operators/take.ts +++ b/src/internal/operators/take.ts @@ -1,7 +1,7 @@ import { Operator } from '../Operator'; import { Subscriber } from '../Subscriber'; import { ArgumentOutOfRangeError } from '..//util/ArgumentOutOfRangeError'; -import { EmptyObservable } from '../observable/EmptyObservable'; +import { empty } from '../observable/empty'; import { Observable } from '../Observable'; import { TeardownLogic } from '../Subscription'; import { MonoTypeOperatorFunction } from '../../internal/types'; @@ -42,7 +42,7 @@ import { MonoTypeOperatorFunction } from '../../internal/types'; export function take(count: number): MonoTypeOperatorFunction { return (source: Observable) => { if (count === 0) { - return new EmptyObservable(); + return empty(); } else { return source.lift(new TakeOperator(count)); } diff --git a/src/internal/operators/takeLast.ts b/src/internal/operators/takeLast.ts index bd64067483..9a053f38bd 100644 --- a/src/internal/operators/takeLast.ts +++ b/src/internal/operators/takeLast.ts @@ -1,7 +1,7 @@ import { Operator } from '../Operator'; import { Subscriber } from '../Subscriber'; import { ArgumentOutOfRangeError } from '..//util/ArgumentOutOfRangeError'; -import { EmptyObservable } from '../observable/EmptyObservable'; +import { empty } from '../observable/empty'; import { Observable } from '../Observable'; import { TeardownLogic } from '../Subscription'; import { MonoTypeOperatorFunction } from '../../internal/types'; @@ -45,7 +45,7 @@ import { MonoTypeOperatorFunction } from '../../internal/types'; export function takeLast(count: number): MonoTypeOperatorFunction { return function takeLastOperatorFunction(source: Observable): Observable { if (count === 0) { - return new EmptyObservable(); + return empty(); } else { return source.lift(new TakeLastOperator(count)); }