Skip to content

Commit

Permalink
feat(bindCallback): remove result selector
Browse files Browse the repository at this point in the history
BREAKING CHANGE: removes result selector, use `map` instead: `bindCallback(fn1, fn2)()` becomes `bindCallback(fn1)().pipe(map(fn2))`
  • Loading branch information
benlesh committed Mar 8, 2018
1 parent ee08d51 commit 2535641
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 181 deletions.
117 changes: 8 additions & 109 deletions spec/observables/bindCallback-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,40 +60,6 @@ describe('bindCallback', () => {
expect(results).to.deep.equal([5, 'done']);
});

it('should emit one value chosen by a selector', () => {
function callback(datum: number, cb: Function) {
cb(datum);
}
const boundCallback = bindCallback(callback, (datum: number) => datum);
const results: Array<string|number> = [];

boundCallback(42)
.subscribe((x: number) => {
results.push(x);
}, null, () => {
results.push('done');
});

expect(results).to.deep.equal([42, 'done']);
});

it('should emit an error when the selector throws', () => {
const expected = new Error('Yikes!');
function callback(cb: Function) {
cb(42);
}
const boundCallback = bindCallback(callback, (err: any) => { throw expected; });

boundCallback()
.subscribe(() => {
throw new Error('should not next');
}, (err: any) => {
expect(err).to.equal(expected);
}, () => {
throw new Error('should not complete');
});
});

it('should not emit, throw or complete if immediately unsubscribed', (done: MochaDone) => {
const nextSpy = sinon.spy();
const throwSpy = sinon.spy();
Expand Down Expand Up @@ -125,7 +91,7 @@ describe('bindCallback', () => {
function callback(cb: Function) {
cb();
}
const boundCallback = bindCallback(callback, null, rxTestScheduler);
const boundCallback = bindCallback(callback, rxTestScheduler);
const results: Array<string|number> = [];

boundCallback()
Expand All @@ -144,7 +110,7 @@ describe('bindCallback', () => {
function callback(datum: number, cb: Function) {
cb(datum);
}
const boundCallback = bindCallback<number>(callback, null, rxTestScheduler);
const boundCallback = bindCallback<number>(callback, rxTestScheduler);
const results: Array<string|number> = [];

boundCallback(42)
Expand All @@ -164,7 +130,7 @@ describe('bindCallback', () => {
cb(this.datum);
}

const boundCallback = bindCallback(callback, null, rxTestScheduler);
const boundCallback = bindCallback(callback, rxTestScheduler);
const results: Array<string|number> = [];

boundCallback.apply({ datum: 5 })
Expand All @@ -184,7 +150,7 @@ describe('bindCallback', () => {
function callback(datum: number, cb: Function): never {
throw expected;
}
const boundCallback = bindCallback(callback, null, rxTestScheduler);
const boundCallback = bindCallback(callback, rxTestScheduler);

boundCallback(42)
.subscribe(x => {
Expand All @@ -198,56 +164,11 @@ describe('bindCallback', () => {
rxTestScheduler.flush();
});

it('should error if selector throws', () => {
const expected = new Error('what? a selector? I don\'t think so');
function callback(datum: number, cb: Function) {
cb(datum);
}
function selector() {
throw expected;
}
const boundCallback = bindCallback(callback, selector, rxTestScheduler);

boundCallback(42)
.subscribe((x: any) => {
throw new Error('should not next');
}, (err: any) => {
expect(err).to.equal(expected);
}, () => {
throw new Error('should not complete');
});

rxTestScheduler.flush();
});

it('should use a selector', () => {
function callback(datum: number, cb: Function) {
cb(datum);
}
function selector(x: number) {
return x + '!!!';
}
const boundCallback = bindCallback(callback, selector, rxTestScheduler);
const results: Array<string|number> = [];

boundCallback(42)
.subscribe((x: string) => {
results.push(x);
}, null, () => {
results.push('done');
});

rxTestScheduler.flush();

expect(results).to.deep.equal(['42!!!', 'done']);
});
});

it('should pass multiple inner arguments as an array', () => {
function callback(datum: number, cb: Function) {
cb(datum, 1, 2, 3);
}
const boundCallback = bindCallback<number[]>(callback, null, rxTestScheduler);
const boundCallback = bindCallback<number[]>(callback, rxTestScheduler);
const results: Array<string|number[]> = [];

boundCallback(42)
Expand All @@ -262,36 +183,13 @@ describe('bindCallback', () => {
expect(results).to.deep.equal([[42, 1, 2, 3], 'done']);
});

it('should pass multiple inner arguments to the selector if there is one', () => {
function callback(datum: number, cb: Function) {
cb(datum, 1, 2, 3);
}
function selector(a: number, b: number, c: number, d: number) {
expect([a, b, c, d]).to.deep.equal([42, 1, 2, 3]);
return a + b + c + d;
}
const boundCallback = bindCallback(callback, selector, rxTestScheduler);
const results: Array<string|number> = [];

boundCallback(42)
.subscribe((x: number) => {
results.push(x);
}, null, () => {
results.push('done');
});

rxTestScheduler.flush();

expect(results).to.deep.equal([48, 'done']);
});

it('should cache value for next subscription and not call callbackFunc again', () => {
let calls = 0;
function callback(datum: number, cb: Function) {
calls++;
cb(datum);
}
const boundCallback = bindCallback<number>(callback, null, rxTestScheduler);
const boundCallback = bindCallback<number>(callback, rxTestScheduler);
const results1: Array<number|string> = [];
const results2: Array<number|string> = [];

Expand Down Expand Up @@ -322,7 +220,7 @@ describe('bindCallback', () => {
calls++;
cb(datum);
}
const boundCallback = bindCallback(callback, null, rxTestScheduler);
const boundCallback = bindCallback(callback, rxTestScheduler);
const results1: Array<number|string> = [];

const source = boundCallback(42);
Expand All @@ -339,4 +237,5 @@ describe('bindCallback', () => {

expect(calls).to.equal(0);
});
});
});
86 changes: 14 additions & 72 deletions src/internal/observable/bindCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,15 @@ import { Subscriber } from '../Subscriber';
import { Action } from '../scheduler/Action';

// tslint:disable:max-line-length
export function bindCallback(callbackFunc: (callback: () => any) => any, selector?: void, scheduler?: IScheduler): () => Observable<void>;
export function bindCallback<R>(callbackFunc: (callback: (result: R) => any) => any, selector?: void, scheduler?: IScheduler): () => Observable<R>;
export function bindCallback<T, R>(callbackFunc: (v1: T, callback: (result: R) => any) => any, selector?: void, scheduler?: IScheduler): (v1: T) => Observable<R>;
export function bindCallback<T, T2, R>(callbackFunc: (v1: T, v2: T2, callback: (result: R) => any) => any, selector?: void, scheduler?: IScheduler): (v1: T, v2: T2) => Observable<R>;
export function bindCallback<T, T2, T3, R>(callbackFunc: (v1: T, v2: T2, v3: T3, callback: (result: R) => any) => any, selector?: void, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3) => Observable<R>;
export function bindCallback<T, T2, T3, T4, R>(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, callback: (result: R) => any) => any, selector?: void, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3, v4: T4) => Observable<R>;
export function bindCallback<T, T2, T3, T4, T5, R>(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, callback: (result: R) => any) => any, selector?: void, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5) => Observable<R>;
export function bindCallback<T, T2, T3, T4, T5, T6, R>(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, callback: (result: R) => any) => any, selector?: void, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6) => Observable<R>;
export function bindCallback<R>(callbackFunc: (callback: (...args: any[]) => any) => any, selector: (...args: any[]) => R, scheduler?: IScheduler): () => Observable<R>;
export function bindCallback<T, R>(callbackFunc: (v1: T, callback: (...args: any[]) => any) => any, selector: (...args: any[]) => R, scheduler?: IScheduler): (v1: T) => Observable<R>;
export function bindCallback<T, T2, R>(callbackFunc: (v1: T, v2: T2, callback: (...args: any[]) => any) => any, selector: (...args: any[]) => R, scheduler?: IScheduler): (v1: T, v2: T2) => Observable<R>;
export function bindCallback<T, T2, T3, R>(callbackFunc: (v1: T, v2: T2, v3: T3, callback: (...args: any[]) => any) => any, selector: (...args: any[]) => R, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3) => Observable<R>;
export function bindCallback<T, T2, T3, T4, R>(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, callback: (...args: any[]) => any) => any, selector: (...args: any[]) => R, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3, v4: T4) => Observable<R>;
export function bindCallback<T, T2, T3, T4, T5, R>(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, callback: (...args: any[]) => any) => any, selector: (...args: any[]) => R, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5) => Observable<R>;
export function bindCallback<T, T2, T3, T4, T5, T6, R>(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, callback: (...args: any[]) => any) => any, selector: (...args: any[]) => R, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6) => Observable<R>;
export function bindCallback<T>(callbackFunc: Function, selector?: void, scheduler?: IScheduler): (...args: any[]) => Observable<T>;
export function bindCallback<T>(callbackFunc: Function, selector?: (...args: any[]) => T, scheduler?: IScheduler): (...args: any[]) => Observable<T>;
export function bindCallback(callbackFunc: (callback: () => any) => any, scheduler?: IScheduler): () => Observable<void>;
export function bindCallback<R>(callbackFunc: (callback: (result: R) => any) => any, scheduler?: IScheduler): () => Observable<R>;
export function bindCallback<T, R>(callbackFunc: (v1: T, callback: (result: R) => any) => any, scheduler?: IScheduler): (v1: T) => Observable<R>;
export function bindCallback<T, T2, R>(callbackFunc: (v1: T, v2: T2, callback: (result: R) => any) => any, scheduler?: IScheduler): (v1: T, v2: T2) => Observable<R>;
export function bindCallback<T, T2, T3, R>(callbackFunc: (v1: T, v2: T2, v3: T3, callback: (result: R) => any) => any, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3) => Observable<R>;
export function bindCallback<T, T2, T3, T4, R>(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, callback: (result: R) => any) => any, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3, v4: T4) => Observable<R>;
export function bindCallback<T, T2, T3, T4, T5, R>(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, callback: (result: R) => any) => any, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5) => Observable<R>;
export function bindCallback<T, T2, T3, T4, T5, T6, R>(callbackFunc: (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6, callback: (result: R) => any) => any, scheduler?: IScheduler): (v1: T, v2: T2, v3: T3, v4: T4, v5: T5, v6: T6) => Observable<R>;
export function bindCallback<T>(callbackFunc: Function, scheduler?: IScheduler): (...args: any[]) => Observable<T>;
// tslint:enable:max-line-length

/**
Expand All @@ -48,14 +40,6 @@ export function bindCallback<T>(callbackFunc: Function, selector?: (...args: any
* function is subscribed. This means if `func` makes an AJAX request, that request
* will be made every time someone subscribes to the resulting Observable, but not before.
*
* Optionally, a selector function can be passed to `bindObservable`. The selector function
* takes the same arguments as the callback and returns the value that will be emitted by the Observable.
* Even though by default multiple arguments passed to callback appear in the stream as an array
* the selector function will be called with arguments directly, just as the callback would.
* This means you can imagine the default selector (when one is not provided explicitly)
* as a function that aggregates all its arguments into an array, or simply returns first argument
* if there is only one.
*
* The last optional parameter - {@link Scheduler} - can be used to control when the call
* to `func` happens after someone subscribes to Observable, as well as when results
* passed to callback will be emitted. By default, the subscription to an Observable calls `func`
Expand Down Expand Up @@ -107,19 +91,6 @@ export function bindCallback<T>(callbackFunc: Function, selector?: (...args: any
* });
*
*
* @example <caption>Use bindCallback with a selector function</caption>
* someFunction((a, b, c) => {
* console.log(a); // 'a'
* console.log(b); // 'b'
* console.log(c); // 'c'
* });
*
* const boundSomeFunction = bindCallback(someFunction, (a, b, c) => a + b + c);
* boundSomeFunction().subscribe(value => {
* console.log(value) // 'abc'
* });
*
*
* @example <caption>Compare behaviour with and without async Scheduler</caption>
* function iCallMyCallbackSynchronously(cb) {
* cb();
Expand Down Expand Up @@ -149,24 +120,20 @@ export function bindCallback<T>(callbackFunc: Function, selector?: (...args: any
* @see {@link fromPromise}
*
* @param {function} func A function with a callback as the last parameter.
* @param {function} [selector] A function which takes the arguments from the
* callback and maps them to a value that is emitted on the output Observable.
* @param {Scheduler} [scheduler] The scheduler on which to schedule the
* callbacks.
* @return {function(...params: *): Observable} A function which returns the
* Observable that delivers the same values the callback would deliver.
* @name bindCallback
*/
export function bindCallback<T>(callbackFunc: Function,
selector: Function | void = undefined,
scheduler?: IScheduler): (...args: any[]) => Observable<T> {
return function (this: any, ...args: any[]): Observable<T> {
const context = this;
let subject: AsyncSubject<T>;
const params = {
context,
subject,
selector,
callbackFunc,
scheduler,
};
Expand All @@ -175,20 +142,8 @@ export function bindCallback<T>(callbackFunc: Function,
if (!subject) {
subject = new AsyncSubject<T>();
const handler = (...innerArgs: any[]) => {
if (selector) {
let result;
try {
result = selector(...innerArgs);
} catch (err) {
subject.error(err);
return;
}
subject.next(result);
subject.complete();
} else {
subject.next(innerArgs.length <= 1 ? innerArgs[0] : innerArgs);
subject.complete();
}
subject.next(innerArgs.length <= 1 ? innerArgs[0] : innerArgs);
subject.complete();
};

try {
Expand Down Expand Up @@ -219,32 +174,19 @@ interface ParamsContext<T> {
scheduler: IScheduler;
context: any;
subject: AsyncSubject<T>;
selector: Function | void;
}

function dispatch<T>(this: Action<DispatchState<T>>, state: DispatchState<T>) {
const self = this;
const { args, subscriber, params } = state;
const { callbackFunc, context, selector, scheduler } = params;
const { callbackFunc, context, scheduler } = params;
let { subject } = params;
if (!subject) {
subject = params.subject = new AsyncSubject<T>();

const handler = (...innerArgs: any[]) => {
if (selector) {
let result;
try {
result = selector(...innerArgs);
} catch (err) {
this.add(scheduler.schedule<ErrorState<T>>(dispatchError, 0, { err, subject }));
return;
}

this.add(scheduler.schedule<NextState<T>>(dispatchNext, 0, { value: result, subject }));
} else {
const value = innerArgs.length <= 1 ? innerArgs[0] : innerArgs;
this.add(scheduler.schedule<NextState<T>>(dispatchNext, 0, { value, subject }));
}
const value = innerArgs.length <= 1 ? innerArgs[0] : innerArgs;
this.add(scheduler.schedule<NextState<T>>(dispatchNext, 0, { value, subject }));
};

try {
Expand Down

0 comments on commit 2535641

Please sign in to comment.