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
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
Original callback | Promisified result |
|---|---|
|
|
|
|
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
// 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 callbackPromisifying your own callback function
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:
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:
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.customor hand-rollednew Promise.Need to feed a promise API into legacy callback code →
util.callbackify.