Skip to content

Commit 3ec315d

Browse files
mpodlasinbenlesh
authored andcommitted
fix(bindNodeCallback): input function context can now be properly set via output function (#2320)
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
1 parent cb91c76 commit 3ec315d

File tree

2 files changed

+50
-7
lines changed

2 files changed

+50
-7
lines changed

spec/observables/bindNodeCallback-spec.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,23 @@ describe('Observable.bindNodeCallback', () => {
2525
expect(results).to.deep.equal([42, 'done']);
2626
});
2727

28+
it('should set context of callback to context of boundCallback', () => {
29+
function callback(cb) {
30+
cb(null, this.datum);
31+
}
32+
const boundCallback = Observable.bindNodeCallback(callback);
33+
const results = [];
34+
35+
boundCallback.call({datum: 42})
36+
.subscribe(
37+
(x: number) => results.push(x),
38+
null,
39+
() => results.push('done')
40+
);
41+
42+
expect(results).to.deep.equal([42, 'done']);
43+
});
44+
2845
it('should emit one value chosen by a selector', () => {
2946
function callback(datum, cb) {
3047
cb(null, datum);
@@ -128,6 +145,25 @@ describe('Observable.bindNodeCallback', () => {
128145
expect(results).to.deep.equal([42, 'done']);
129146
});
130147

148+
it('should set context of callback to context of boundCallback', () => {
149+
function callback(cb) {
150+
cb(null, this.datum);
151+
}
152+
const boundCallback = Observable.bindNodeCallback(callback, null, rxTestScheduler);
153+
const results = [];
154+
155+
boundCallback.call({datum: 42})
156+
.subscribe(
157+
(x: number) => results.push(x),
158+
null,
159+
() => results.push('done')
160+
);
161+
162+
rxTestScheduler.flush();
163+
164+
expect(results).to.deep.equal([42, 'done']);
165+
});
166+
131167
it('should error if callback throws', () => {
132168
const expected = new Error('haha no callback for you');
133169
function callback(datum, cb) {

src/observable/BoundNodeCallbackObservable.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,16 @@ export class BoundNodeCallbackObservable<T> extends Observable<T> {
3939
* last parameter must be a callback function that `func` calls when it is
4040
* done. The callback function is expected to follow Node.js conventions,
4141
* where the first argument to the callback is an error, while remaining
42-
* arguments are the callback result. The output of `bindNodeCallback` is a
42+
* arguments are the callback result.
43+
*
44+
* The output of `bindNodeCallback` is a
4345
* function that takes the same parameters as `func`, except the last one (the
4446
* callback). When the output function is called with arguments, it will
4547
* return an Observable where the results will be delivered to.
4648
*
49+
* As in {@link bindCallback}, context (`this` property) of input function will be set to context
50+
* of returned function, when it is called.
51+
*
4752
* @example <caption>Read a file from the filesystem and get the data as an Observable</caption>
4853
* import * as fs from 'fs';
4954
* var readFileAsObservable = Rx.Observable.bindNodeCallback(fs.readFile);
@@ -69,14 +74,15 @@ export class BoundNodeCallbackObservable<T> extends Observable<T> {
6974
static create<T>(func: Function,
7075
selector: Function | void = undefined,
7176
scheduler?: IScheduler): (...args: any[]) => Observable<T> {
72-
return (...args: any[]): Observable<T> => {
73-
return new BoundNodeCallbackObservable<T>(func, <any>selector, args, scheduler);
77+
return function(this: any, ...args: any[]): Observable<T> {
78+
return new BoundNodeCallbackObservable<T>(func, <any>selector, args, this, scheduler);
7479
};
7580
}
7681

7782
constructor(private callbackFunc: Function,
7883
private selector: Function,
7984
private args: any[],
85+
private context: any,
8086
public scheduler: IScheduler) {
8187
super();
8288
}
@@ -113,26 +119,27 @@ export class BoundNodeCallbackObservable<T> extends Observable<T> {
113119
// use named function instance to avoid closure.
114120
(<any>handler).source = this;
115121

116-
const result = tryCatch(callbackFunc).apply(this, args.concat(handler));
122+
const result = tryCatch(callbackFunc).apply(this.context, args.concat(handler));
117123
if (result === errorObject) {
118124
subject.error(errorObject.e);
119125
}
120126
}
121127
return subject.subscribe(subscriber);
122128
} else {
123-
return scheduler.schedule(dispatch, 0, { source: this, subscriber });
129+
return scheduler.schedule(dispatch, 0, { source: this, subscriber, context: this.context });
124130
}
125131
}
126132
}
127133

128134
interface DispatchState<T> {
129135
source: BoundNodeCallbackObservable<T>;
130136
subscriber: Subscriber<T>;
137+
context: any;
131138
}
132139

133140
function dispatch<T>(this: Action<DispatchState<T>>, state: DispatchState<T>) {
134141
const self = (<Subscription> this);
135-
const { source, subscriber } = state;
142+
const { source, subscriber, context } = state;
136143
// XXX: cast to `any` to access to the private field in `source`.
137144
const { callbackFunc, args, scheduler } = source as any;
138145
let subject = source.subject;
@@ -162,7 +169,7 @@ function dispatch<T>(this: Action<DispatchState<T>>, state: DispatchState<T>) {
162169
// use named function to pass values in without closure
163170
(<any>handler).source = source;
164171

165-
const result = tryCatch(callbackFunc).apply(this, args.concat(handler));
172+
const result = tryCatch(callbackFunc).apply(context, args.concat(handler));
166173
if (result === errorObject) {
167174
subject.error(errorObject.e);
168175
}

0 commit comments

Comments
 (0)