Skip to content

Commit

Permalink
Merge pull request #3250 from benlesh/iif-fn
Browse files Browse the repository at this point in the history
Refactor iif impl to be defer-based (plus some file moves/cleanup)
  • Loading branch information
benlesh authored Mar 2, 2018
2 parents 0a3a04a + cd15471 commit 72d9745
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 166 deletions.
6 changes: 3 additions & 3 deletions spec/exports-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { from } from '../src/internal/observable/from';
import { fromEvent } from '../src/internal/observable/fromEvent';
import { fromEventPattern } from '../src/internal/observable/fromEventPattern';
import { fromPromise } from '../src/internal/observable/fromPromise';
import { _if } from '../src/internal/observable/if';
import { iif } from '../src/internal/observable/iif';
import { interval } from '../src/internal/observable/interval';
import { merge } from '../src/internal/observable/merge';
import { never } from '../src/internal/observable/never';
Expand Down Expand Up @@ -70,8 +70,8 @@ describe('exports', () => {
expect(fromPromise).to.equal(Rx.Observable.fromPromise);
});

it('should have rxjs/observable/if', () => {
expect(_if).to.equal(Rx.Observable.if);
it('should have rxjs/observable/iif', () => {
expect(iif).to.equal(Rx.Observable.if);
});

it('should have rxjs/observable/interval', () => {
Expand Down
28 changes: 13 additions & 15 deletions spec/observables/if-spec.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,33 @@
import { expect } from 'chai';
import * as Rx from '../../src/Rx';
import { iif, of } from '../../src';
import { expectObservable } from '../helpers/marble-testing';

const Observable = Rx.Observable;

describe('Observable.if', () => {
describe('iif', () => {
it('should subscribe to thenSource when the conditional returns true', () => {
const e1 = Observable.if(() => true, Observable.of('a'));
const e1 = iif(() => true, of('a'));
const expected = '(a|)';

expectObservable(e1).toBe(expected);
});

it('should subscribe to elseSource when the conditional returns false', () => {
const e1 = Observable.if(() => false, Observable.of('a'), Observable.of('b'));
const e1 = iif(() => false, of('a'), of('b'));
const expected = '(b|)';

expectObservable(e1).toBe(expected);
});

it('should complete without an elseSource when the conditional returns false', () => {
const e1 = Observable.if(() => false, Observable.of('a'));
const e1 = iif(() => false, of('a'));
const expected = '|';

expectObservable(e1).toBe(expected);
});

it('should raise error when conditional throws', () => {
const e1 = Observable.if(<any>(() => {
const e1 = iif(<any>(() => {
throw 'error';
}), Observable.of('a'));
}), of('a'));

const expected = '#';

Expand All @@ -38,7 +36,7 @@ describe('Observable.if', () => {

it('should accept resolved promise as thenSource', (done: MochaDone) => {
const expected = 42;
const e1 = Observable.if(() => true, new Promise((resolve: any) => { resolve(expected); }));
const e1 = iif(() => true, new Promise((resolve: any) => { resolve(expected); }));

e1.subscribe(x => {
expect(x).to.equal(expected);
Expand All @@ -51,8 +49,8 @@ describe('Observable.if', () => {

it('should accept resolved promise as elseSource', (done: MochaDone) => {
const expected = 42;
const e1 = Observable.if(() => false,
Observable.of('a'),
const e1 = iif(() => false,
of('a'),
new Promise((resolve: any) => { resolve(expected); }));

e1.subscribe(x => {
Expand All @@ -66,8 +64,8 @@ describe('Observable.if', () => {

it('should accept rejected promise as elseSource', (done: MochaDone) => {
const expected = 42;
const e1 = Observable.if(() => false,
Observable.of('a'),
const e1 = iif(() => false,
of('a'),
new Promise((resolve: any, reject: any) => { reject(expected); }));

e1.subscribe(x => {
Expand All @@ -82,7 +80,7 @@ describe('Observable.if', () => {

it('should accept rejected promise as thenSource', (done: MochaDone) => {
const expected = 42;
const e1 = Observable.if(() => true, new Promise((resolve: any, reject: any) => { reject(expected); }));
const e1 = iif(() => true, new Promise((resolve: any, reject: any) => { reject(expected); }));

e1.subscribe(x => {
done(new Error('should not be called'));
Expand Down
5 changes: 3 additions & 2 deletions src/add/observable/if.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Observable } from '../../internal/Observable';
import { _if } from '../../internal/observable/if';
import { iif } from '../../internal/observable/iif';

Observable.if = _if;
//tslint:disable-next-line:no-any TypeScript doesn't like `if`
(Observable as any).if = iif;
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export { from } from './internal/observable/from';
export { fromEvent } from './internal/observable/fromEvent';
export { fromEventPattern } from './internal/observable/fromEventPattern';
export { generate } from './internal/observable/generate';
export { _if as iif } from './internal/observable/if';
export { iif } from './internal/observable/iif';
export { interval } from './internal/observable/interval';
export { merge } from './internal/observable/merge';
export { never } from './internal/observable/never';
Expand Down
5 changes: 3 additions & 2 deletions src/internal/Observable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Subscription } from './Subscription';
import { TeardownLogic } from './types';
import { root } from './util/root';
import { toSubscriber } from './util/toSubscriber';
import { IfObservable } from './observable/IfObservable';
import { iif } from './observable/iif';
import { observable as Symbol_observable } from '../internal/symbol/observable';
import { OperatorFunction, PartialObserver, Subscribable } from '../internal/types';
import { pipeFromArray } from './util/pipe';
Expand Down Expand Up @@ -248,8 +248,9 @@ export class Observable<T> implements Subscribable<T> {
return this.source.subscribe(subscriber);
}

// TODO(benlesh): determine if this is still necessary
// `if` and `throw` are special snow flakes, the compiler sees them as reserved words
static if: typeof IfObservable.create;
static if: typeof iif;

/**
* An interop point defined by the es7-observable spec https://github.com/zenparsing/es-observable
Expand Down
140 changes: 0 additions & 140 deletions src/internal/observable/IfObservable.ts

This file was deleted.

3 changes: 0 additions & 3 deletions src/internal/observable/if.ts

This file was deleted.

93 changes: 93 additions & 0 deletions src/internal/observable/iif.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Observable } from '../Observable';
import { defer } from './defer';
import { EMPTY } from './empty';
import { SubscribableOrPromise } from '../types';

/**
* Decides at subscription time which Observable will actually be subscribed.
*
* <span class="informal">`If` statement for Observables.</span>
*
* `if` accepts a condition function and two Observables. When
* an Observable returned by the operator is subscribed, condition function will be called.
* Based on what boolean it returns at that moment, consumer will subscribe either to
* the first Observable (if condition was true) or to the second (if condition was false). Condition
* function may also not return anything - in that case condition will be evaluated as false and
* second Observable will be subscribed.
*
* Note that Observables for both cases (true and false) are optional. If condition points to an Observable that
* was left undefined, resulting stream will simply complete immediately. That allows you to, rather
* then controlling which Observable will be subscribed, decide at runtime if consumer should have access
* to given Observable or not.
*
* If you have more complex logic that requires decision between more than two Observables, {@link defer}
* will probably be a better choice. Actually `if` can be easily implemented with {@link defer}
* and exists only for convenience and readability reasons.
*
*
* @example <caption>Change at runtime which Observable will be subscribed</caption>
* let subscribeToFirst;
* const firstOrSecond = Rx.Observable.if(
* () => subscribeToFirst,
* Rx.Observable.of('first'),
* Rx.Observable.of('second')
* );
*
* subscribeToFirst = true;
* firstOrSecond.subscribe(value => console.log(value));
*
* // Logs:
* // "first"
*
* subscribeToFirst = false;
* firstOrSecond.subscribe(value => console.log(value));
*
* // Logs:
* // "second"
*
*
* @example <caption>Control an access to an Observable</caption>
* let accessGranted;
* const observableIfYouHaveAccess = Rx.Observable.if(
* () => accessGranted,
* Rx.Observable.of('It seems you have an access...') // Note that only one Observable is passed to the operator.
* );
*
* accessGranted = true;
* observableIfYouHaveAccess.subscribe(
* value => console.log(value),
* err => {},
* () => console.log('The end')
* );
*
* // Logs:
* // "It seems you have an access..."
* // "The end"
*
* accessGranted = false;
* observableIfYouHaveAccess.subscribe(
* value => console.log(value),
* err => {},
* () => console.log('The end')
* );
*
* // Logs:
* // "The end"
*
* @see {@link defer}
*
* @param {function(): boolean} condition Condition which Observable should be chosen.
* @param {Observable} [trueObservable] An Observable that will be subscribed if condition is true.
* @param {Observable} [falseObservable] An Observable that will be subscribed if condition is false.
* @return {Observable} Either first or second Observable, depending on condition.
* @static true
* @name iif
* @owner Observable
*/
export function iif<T, F>(
condition: () => boolean,
trueResult: SubscribableOrPromise<T> = EMPTY,
falseResult: SubscribableOrPromise<F> = EMPTY
): Observable<T|F> {
return defer<T|F>(() => condition() ? trueResult : falseResult);
}

0 comments on commit 72d9745

Please sign in to comment.