Skip to main content

bindNodeCallback

Converts a Node.js-style callback API to a function that returns an Observable. Specifically designed for callbacks that follow the callback(error, result) pattern.

Import

import { bindNodeCallback } from 'rxjs';

Type Signature

function bindNodeCallback<A extends readonly unknown[], R extends readonly unknown[]>(
  callbackFunc: (...args: [...A, (err: any, ...res: R) => void]) => void,
  schedulerLike?: SchedulerLike
): (...arg: A) => Observable<R extends [] ? void : R extends [any] ? R[0] : R>;

Parameters

callbackFunc
Function
required
A function with a Node.js-style callback as the last parameter. The callback should have the signature (error, result) => void.
schedulerLike
SchedulerLike
Optional scheduler to control when the function is called and when results are emitted.

Returns

Function
(...args) => Observable
A function that takes the same parameters as callbackFunc (except the callback). Returns an Observable that:
  • Emits the result value(s) if no error occurs
  • Errors if the first callback parameter is truthy

Description

bindNodeCallback is similar to bindCallback, but expects the callback to follow Node.js conventions:
  1. The first parameter is an error object (or null/undefined if no error)
  2. Subsequent parameters are the success values
If the error parameter is truthy, the Observable will error. Otherwise, it will emit the success values.

Examples

Read File from Filesystem

import { bindNodeCallback } from 'rxjs';
import * as fs from 'fs';

const readFileAsObservable = bindNodeCallback(fs.readFile);
const result$ = readFileAsObservable('./roadNames.txt', 'utf8');

result$.subscribe({
  next: data => console.log('File contents:', data),
  error: err => console.error('Error reading file:', err),
  complete: () => console.log('Done')
});

Multiple Result Values

When the callback receives multiple values (after the error), they are emitted as an array:
import { bindNodeCallback } from 'rxjs';

function someFunction(
  callback: (err: any, a: number, b: string) => void
) {
  callback(null, 5, 'some string');
}

const boundSomeFunction = bindNodeCallback(someFunction);

boundSomeFunction().subscribe(value => {
  console.log(value); // [5, 'some string']
});

Error Handling

import { bindNodeCallback } from 'rxjs';
import { catchError, of } from 'rxjs';
import * as fs from 'fs';

const readFile$ = bindNodeCallback(fs.readFile);

readFile$('./nonexistent.txt', 'utf8').pipe(
  catchError(error => {
    console.error('File not found:', error.message);
    return of('Default content');
  })
).subscribe(content => {
  console.log('Content:', content);
});

Common Use Cases

Database Queries

import { bindNodeCallback } from 'rxjs';
import { switchMap, map } from 'rxjs/operators';

// Assuming a database library with Node.js callbacks
const query = bindNodeCallback(db.query.bind(db));

query('SELECT * FROM users WHERE id = ?', [userId]).pipe(
  map(rows => rows[0]),
  switchMap(user => {
    return query('SELECT * FROM posts WHERE user_id = ?', [user.id]);
  })
).subscribe(posts => {
  console.log('User posts:', posts);
});

File System Operations

import { bindNodeCallback } from 'rxjs';
import * as fs from 'fs';

const readFile$ = bindNodeCallback(fs.readFile);

readFile$('./config.json', 'utf8').subscribe({
  next: content => {
    const config = JSON.parse(content);
    console.log('Config:', config);
  },
  error: err => console.error('Error:', err)
});

Crypto Operations

import { bindNodeCallback } from 'rxjs';
import * as crypto from 'crypto';

const randomBytes$ = bindNodeCallback(crypto.randomBytes);

randomBytes$(32).subscribe({
  next: buffer => {
    const token = buffer.toString('hex');
    console.log('Random token:', token);
  },
  error: err => console.error('Crypto error:', err)
});

HTTP Request (Node.js)

import { bindNodeCallback } from 'rxjs';
import * as https from 'https';
import { map } from 'rxjs/operators';

function httpGet(url: string, callback: (err: any, data: string) => void) {
  https.get(url, (res) => {
    let data = '';
    res.on('data', chunk => data += chunk);
    res.on('end', () => callback(null, data));
    res.on('error', err => callback(err, ''));
  });
}

const httpGet$ = bindNodeCallback(httpGet);

httpGet$('https://api.github.com/users/octocat').pipe(
  map(response => JSON.parse(response))
).subscribe(user => {
  console.log('User:', user.login);
});

Important Differences from bindCallback

Error Detection: bindNodeCallback treats the first parameter as an error indicator. Any truthy value (including non-zero numbers, non-empty strings, or true) will cause the Observable to error.
import { bindNodeCallback } from 'rxjs';

// This will ERROR because first parameter is truthy
function badCallback(cb: (err: any) => void) {
  cb(1); // Non-zero number is truthy
}

const bound = bindNodeCallback(badCallback);
bound().subscribe({
  next: () => console.log('Success'), // Won't be called
  error: err => console.error('Error:', err) // Will be called with value 1
});

Regular vs Node.js Style

import { bindNodeCallback } from 'rxjs';

// Callback signature: (error, result) => void
const nodeStyleFn = (x: number, cb: (err: any, result: number) => void) => {
  if (x < 0) {
    cb(new Error('Negative number'), 0);
  } else {
    cb(null, x * 2);
  }
};

const bound = bindNodeCallback(nodeStyleFn);

bound(5).subscribe(
  result => console.log('Result:', result), // Result: 10
  err => console.error('Error:', err)
);

Behavior with Scheduler

import { bindNodeCallback, asyncScheduler } from 'rxjs';
import * as fs from 'fs';

// Without scheduler - synchronous subscription
const readFileSync = bindNodeCallback(fs.readFile);

// With scheduler - asynchronous subscription
const readFileAsync = bindNodeCallback(fs.readFile, asyncScheduler);

readFileAsync('./file.txt', 'utf8').subscribe(content => {
  console.log('Content loaded asynchronously');
});

console.log('This logs first');

Tips

Use bindNodeCallback for any API that follows the (err, result) callback pattern, even outside of Node.js.
The Observable completes immediately after emitting the result, making it perfect for one-time async operations.
Don’t use bindNodeCallback with callbacks that are called multiple times. For that, use fromEvent or fromEventPattern.
  • bindCallback - For regular callbacks without error parameter
  • from - Convert promises to Observables
  • defer - Lazy Observable creation
  • fromEvent - For event emitters

See Also