JavaScript Basics: Adapt Error-First Callbacks with promisify
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 1Run 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.mjsObserved output:
Doubled: 12
Invalid input: rejected
Bound method: 12Read 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_dirIf the directory is not empty, inspect the remaining files rather than deleting unknown contents.
Acknowledgements
This post was created with assistance from AI.