From b4b70c68486811e66bc77f938831028ab653425e Mon Sep 17 00:00:00 2001 From: Mateusz Podlasin Date: Thu, 2 Feb 2017 18:12:45 +0100 Subject: [PATCH] fix(bindNodeCallback): properly set context of input function Set context of input function to context of output function, so that context can be controlled at call time and underlying observable is not available in input function --- spec/observables/bindNodeCallback-spec.ts | 36 +++++++++++++++++++ src/observable/BoundNodeCallbackObservable.ts | 21 +++++++---- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/spec/observables/bindNodeCallback-spec.ts b/spec/observables/bindNodeCallback-spec.ts index 2aa3c67b6e..15a9e53d32 100644 --- a/spec/observables/bindNodeCallback-spec.ts +++ b/spec/observables/bindNodeCallback-spec.ts @@ -25,6 +25,23 @@ describe('Observable.bindNodeCallback', () => { expect(results).to.deep.equal([42, 'done']); }); + it('should set context of callback to context of boundCallback', () => { + function callback(cb) { + cb(null, this.datum); + } + const boundCallback = Observable.bindNodeCallback(callback); + const results = []; + + boundCallback.call({datum: 42}) + .subscribe( + (x: number) => results.push(x), + null, + () => results.push('done') + ); + + expect(results).to.deep.equal([42, 'done']); + }); + it('should emit one value chosen by a selector', () => { function callback(datum, cb) { cb(null, datum); @@ -128,6 +145,25 @@ describe('Observable.bindNodeCallback', () => { expect(results).to.deep.equal([42, 'done']); }); + it('should set context of callback to context of boundCallback', () => { + function callback(cb) { + cb(null, this.datum); + } + const boundCallback = Observable.bindNodeCallback(callback, null, rxTestScheduler); + const results = []; + + boundCallback.call({datum: 42}) + .subscribe( + (x: number) => results.push(x), + null, + () => results.push('done') + ); + + rxTestScheduler.flush(); + + expect(results).to.deep.equal([42, 'done']); + }); + it('should error if callback throws', () => { const expected = new Error('haha no callback for you'); function callback(datum, cb) { diff --git a/src/observable/BoundNodeCallbackObservable.ts b/src/observable/BoundNodeCallbackObservable.ts index 63e00e2d74..b3b3877df9 100644 --- a/src/observable/BoundNodeCallbackObservable.ts +++ b/src/observable/BoundNodeCallbackObservable.ts @@ -39,11 +39,16 @@ export class BoundNodeCallbackObservable extends Observable { * last parameter must be a callback function that `func` calls when it is * done. The callback function is expected to follow Node.js conventions, * where the first argument to the callback is an error, while remaining - * arguments are the callback result. The output of `bindNodeCallback` is a + * arguments are the callback result. + * + * The output of `bindNodeCallback` is a * function that takes the same parameters as `func`, except the last one (the * callback). When the output function is called with arguments, it will * return an Observable where the results will be delivered to. * + * As in {@link bindCallback}, context (`this` property) of input function will be set to context + * of returned function, when it is called. + * * @example Read a file from the filesystem and get the data as an Observable * import * as fs from 'fs'; * var readFileAsObservable = Rx.Observable.bindNodeCallback(fs.readFile); @@ -69,14 +74,15 @@ export class BoundNodeCallbackObservable extends Observable { static create(func: Function, selector: Function | void = undefined, scheduler?: IScheduler): (...args: any[]) => Observable { - return (...args: any[]): Observable => { - return new BoundNodeCallbackObservable(func, selector, args, scheduler); + return function(this: any, ...args: any[]): Observable { + return new BoundNodeCallbackObservable(func, selector, args, this, scheduler); }; } constructor(private callbackFunc: Function, private selector: Function, private args: any[], + private context: any, public scheduler: IScheduler) { super(); } @@ -113,14 +119,14 @@ export class BoundNodeCallbackObservable extends Observable { // use named function instance to avoid closure. (handler).source = this; - const result = tryCatch(callbackFunc).apply(this, args.concat(handler)); + const result = tryCatch(callbackFunc).apply(this.context, args.concat(handler)); if (result === errorObject) { subject.error(errorObject.e); } } return subject.subscribe(subscriber); } else { - return scheduler.schedule(dispatch, 0, { source: this, subscriber }); + return scheduler.schedule(dispatch, 0, { source: this, subscriber, context: this.context }); } } } @@ -128,11 +134,12 @@ export class BoundNodeCallbackObservable extends Observable { interface DispatchState { source: BoundNodeCallbackObservable; subscriber: Subscriber; + context: any; } function dispatch(this: Action>, state: DispatchState) { const self = ( this); - const { source, subscriber } = state; + const { source, subscriber, context } = state; // XXX: cast to `any` to access to the private field in `source`. const { callbackFunc, args, scheduler } = source as any; let subject = source.subject; @@ -162,7 +169,7 @@ function dispatch(this: Action>, state: DispatchState) { // use named function to pass values in without closure (handler).source = source; - const result = tryCatch(callbackFunc).apply(this, args.concat(handler)); + const result = tryCatch(callbackFunc).apply(context, args.concat(handler)); if (result === errorObject) { subject.error(errorObject.e); }