util.promisify()
Converts an error-first callback function into one that returns a promise instead.
util.promisify(original)- Added in Node.js v8.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$original | Function | A function following the common error-first callback style, i.e. taking an (err, value) => ... callback as its last argument. |
Return value
FunctionA function that takes the same leading arguments as `original` and returns a promise, resolving with the callback's value or rejecting with its error.
Errors
Throws ERR_INVALID_ARG_TYPE if `original` is not a function.
Changelog
- PHP v20.8.0Calling promisify() on a function that already returns a Promise is deprecated.
- PHP v13.12.0, v12.16.2util.promisify.custom is now defined as a shared symbol, also reachable as Symbol.for('nodejs.util.promisify.custom').
util.promisify() is the bridge between Node's original error-first callback convention and async/await. Give it a function whose last argument is (err, value) => ... and you get back a function that takes the same leading arguments and returns a promise.
When to reach for it
Reach for it when you are calling an older library, a C addon binding, or your own callback API from async code. You do not need it for Node's own modules any more — fs, dns, stream, timers and readline all ship promise versions (node:fs/promises, timers/promises, and so on) that are faster and better typed than a promisified wrapper.
What trips people up
- Only the first value survives. A callback invoked as
cb(null, a, b)produces a promise resolving toa;bis dropped. Functions that need to return more declare autil.promisify.customvariant. thisis lost. Promisifying a method detached from its object (promisify(obj.method)) throws once the body touchesthis. Bind first, or call the result with.call(obj, ...).- It assumes a callback is last.
promisify()does not inspect the function; it appends a callback regardless. Passing something that ignores that argument yields a promise that never settles.
Example
When to use it
- Call a third-party library that only exposes error-first callbacks from inside an async function.
- Wrap a C++ addon binding so it can be used with await and try/catch.
- Migrate an old codebase off nested callbacks one function at a time, without rewriting the callees.
Frequently asked questions
Why does my promisified function only return the first value?
Because `util.promisify()` resolves with the single value at `callback(err, value)` and discards anything after it. A callback invoked as `cb(null, a, b)` gives a promise resolving to `a` only. To get both, attach a `util.promisify.custom` implementation to the original function that resolves with an object or array.
Do I still need util.promisify for fs.readFile in modern Node?
No — use `require('node:fs/promises').readFile` instead. Node ships promise-based variants of `fs`, `dns`, `stream`, `timers` and `readline`, and they are faster and better typed than a promisified wrapper. `util.promisify()` is for third-party and legacy callback APIs only.
Why do I get "Cannot read properties of undefined" from a promisified class method?
Because promisifying a detached method loses its `this` binding. Use `util.promisify(obj.method.bind(obj))`, or invoke the result as `promisified.call(obj, ...args)`. `util.promisify()` returns an ordinary function and does nothing to preserve the receiver.
More examples
Rescuing a non-standard callback signature with promisify.custom
When `promisify()` finds a `util.promisify.custom` property on the function it returns that value verbatim and never appends a callback. This is the only way to promisify APIs that take success and error callbacks separately, or that pass more than one success value. Because the symbol comes from the global registry, `Symbol.for('nodejs.util.promisify.custom')` and `util.promisify.custom` are the same symbol.
const util = require('node:util');
// Two callbacks instead of one error-first callback: promisify() cannot
// guess this shape, so the function declares its own promise version.
function doThing(value, onSuccess, onError) {
if (typeof value !== 'number') {
onError(new TypeError('value must be a number'));
return;
}
onSuccess(value * 2);
}
doThing[util.promisify.custom] = (value) =>
new Promise((resolve, reject) => doThing(value, resolve, reject));
const doThingAsync = util.promisify(doThing);
doThingAsync(21).then((result) => console.log('custom:', result));
// Prints: custom: 42
// The symbol is registered globally, so a library can declare it without
// importing node:util at all:
const kCustom = Symbol.for('nodejs.util.promisify.custom');
console.log(kCustom === util.promisify.custom); // trueMethods lose their receiver
`promisify()` returns a plain function, so extracting a method from its object detaches `this` exactly as it would with any other reference. The output shows both fixes returning 2. Binding at promisify time is usually cleaner because the promisified function can then be passed around freely.
const util = require('node:util');
class Store {
constructor() {
this.items = ['a', 'b'];
}
read(callback) {
callback(null, this.items.length);
}
}
const store = new Store();
const naive = util.promisify(store.read);
// naive(); // TypeError: Cannot read properties of undefined (reading 'items')
// Either bind before promisifying...
const bound = util.promisify(store.read.bind(store));
bound().then((n) => console.log('bound:', n)); // bound: 2
// ...or supply the receiver at the call site.
naive.call(store).then((n) => console.log('call:', n)); // call: 2Prefer the built-in promise APIs where they exist
Both reads return identical contents, but `node:fs/promises` avoids the wrapper allocation on every call and carries proper TypeScript types. Reserve `promisify()` for third-party or legacy callback APIs that have no promise counterpart.
import util from 'node:util';
import fs from 'node:fs';
import fsp from 'node:fs/promises';
import { setTimeout as delay } from 'node:timers/promises';
async function main() {
// Works, but redundant:
const readFile = util.promisify(fs.readFile);
const a = await readFile('package.json', 'utf8');
// Preferred: the native promise API.
const b = await fsp.readFile('package.json', 'utf8');
console.log('same contents:', a === b);
// Same story for timers.
await delay(10);
console.log('waited 10ms');
}
main();
Discussion