Skip to content
JavaScript Basics: Adapt Error-First Callbacks with promisify

JavaScript Basics: Adapt Error-First Callbacks with promisify

October 27, 2026

Legacy APIs often finish through a callback whose first argument is an error. util.promisify adapts that convention to a promise. A tiny fixture checks successful results, rejected errors, and a method that requires its receiver.

Prepare a scratch directory

Tested on Linux with Node.js v24.19.0. You need the named runtime, Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. The .mjs extension selects ES modules. Assertions check the demonstrated behavior; no npm packages or remote services are needed.

Keep using the same terminal. Stop if setup fails, before saving the source:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Run the example

Save this as demonstrate-promisify.mjs:

import assert from 'node:assert/strict';
import { promisify } from 'node:util';

function doubleLater(value, callback) {
  queueMicrotask(() => {
    if (!Number.isFinite(value)) {
      callback(new TypeError('finite number required'));
      return;
    }
    callback(null, value * 2);
  });
}

const double = promisify(doubleLater);
assert.equal(await double(6), 12);
console.log('Doubled:', await double(6));
await assert.rejects(double(NaN), {
  name: 'TypeError', message: 'finite number required',
});
console.log('Invalid input: rejected');

const calculator = {
  factor: 3,
  multiply(value, callback) { callback(null, value * this.factor); },
};
const multiply = promisify(calculator.multiply.bind(calculator));
assert.equal(await multiply(4), 12);
console.log('Bound method:', await multiply(4));

Run it:

node demonstrate-promisify.mjs

Observed output:

Doubled: 12
Invalid input: rejected
Bound method: 12

Read the result

A null first callback argument signals success; the next value becomes the promise result. Passing an error rejects the promise, which the test verifies by name and message. Promisifying a method does not automatically retain the object it came from. Binding calculator first preserves this.factor when the wrapper later invokes it.

Apply it carefully

Use this adapter for the expected final error-first callback convention. APIs with multiple success values, separate success/failure callbacks, or custom signatures need a deliberate adapter or promisify.custom. Prefer an existing native promise API when available; promisifying a function that already returns a promise is deprecated in current Node. The wrapper does not move CPU work to another thread or make a synchronous method nonblocking.

The official reference describes the API and its options. The checks above cover the specific fixture, not every possible input or environment.

Clean up

Remove the source and then the empty practice directory:

rm -- demonstrate-promisify.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If the directory is not empty, inspect the remaining files rather than deleting unknown contents.

Acknowledgements

This post was created with assistance from AI.

Last updated on