NodeJSutil.promisify

util.promisify

Plenty of Node and library code still uses error-first callbacks. util.promisify converts any such function into one that returns a promise — so you can await it instead of nesting callbacks. It's the bridge between the old callback world and modern async/await, and it understands Node's conventions out of the box.

The basic conversion

JS
import { promisify } from 'node:util'
import { readFile } from 'node:fs'

const readFileP = promisify(readFile)

// Now usable with await — no callback:
const text = await readFileP('config.json', 'utf8')
console.log(text)

promisify returns a new function with the same arguments minus the trailing callback. Calling it returns a promise that fulfills with the callback's result, or rejects with its error.

What it expects
It only works with error-first, callback-last functions
`promisify` assumes the function's **last** argument is a callback called as `(err, value)`. If the function doesn't follow this exact shape — callback not last, or multiple success values — the conversion misbehaves or loses data. For those, wrap by hand with `new Promise(...)` or define custom behavior (below).

Original callback

Promisified result

fn(args, (err, value) => …)

await fn(args)value

fn(args, (err) => …) (no value)

await fn(args)undefined

callback gets multiple values

Only the first is kept — needs custom handling

callback not last

Not supported directly

Check for a built-in promise API first
Many core modules already ship promise versions
Before promisifying, look for a native promise variant — it's better-tested and often more featureful. `fs` → `node:fs/promises`, `dns` → `node:dns/promises`, `timers` → `node:timers/promises`, `stream` → `node:stream/promises`. Reserve `promisify` for callback APIs that genuinely lack one (older libraries, your own legacy code).

JS
// Don't promisify fs.readFile — just use the promises API:
import { readFile } from 'node:fs/promises'
const text = await readFile('config.json', 'utf8')

// timers/promises instead of promisify(setTimeout):
import { setTimeout as sleep } from 'node:timers/promises'
await sleep(1000)   // pause 1s without a callback
Promisifying your own callback function

JS
import { promisify } from 'node:util'

// A legacy error-first function:
function loadConfig(path, callback) {
  setTimeout(() => {
    if (!path) return callback(new Error('path required'))
    callback(null, { path, loaded: true })
  }, 50)
}

const loadConfigP = promisify(loadConfig)
const cfg = await loadConfigP('app.json')   // { path: 'app.json', loaded: true }
Functions with multiple callback values

Some functions call back with more than one success value — promisify keeps only the first unless you tell it otherwise via the promisify.custom symbol:

JS
import { promisify } from 'node:util'

function getDimensions(id, cb) { cb(null, 1920, 1080) }   // two values!

// Teach promisify to bundle both into an object:
getDimensions[promisify.custom] = (id) =>
  new Promise((resolve) => getDimensions(id, (err, w, h) => resolve({ w, h })))

const dims = await promisify(getDimensions)('img1')   // { w: 1920, h: 1080 }
The reverse: callbackify

Occasionally you need to expose a promise-based function to callback-based code (a legacy API expecting the old shape). util.callbackify does the inverse:

JS
import { callbackify } from 'node:util'

async function fetchUser(id) { return { id, name: 'Ada' } }

const fetchUserCb = callbackify(fetchUser)
fetchUserCb(1, (err, user) => console.log(user.name))   // 'Ada'
When to use what
  • Native promise API exists → use it (fs/promises, timers/promises, …).

  • Callback-only function, standard shape → util.promisify.

  • Callback function with quirks (multiple values, odd order) → promisify.custom or hand-rolled new Promise.

  • Need to feed a promise API into legacy callback code → util.callbackify.

Next
Combine all of this into robust real-world flows — retries, timeouts, concurrency limits: [Async Control-Flow Patterns](/nodejs/async-patterns).