Skip to the solution
EasyPhone Screen6 min read

What is the purpose of the util.promisify function in Node.js?

452 views

The problem

Question presented to candidate: "You are working with an old library that only exposes callback-style APIs (error-first callbacks), and the rest of your codebase uses async/await. What is the standard way to bridge that gap without rewriting the library?"

What a strong answer should cover:

  • util.promisify(fn) takes a function following Node's error-first callback convention β€” fn(...args, (err, result) => {...}) β€” and returns a new function that instead returns a Promise, resolving with result or rejecting with err.
  • πŸ“Œ This is verifiably correct for both outcomes, not just the happy path: a promisified function whose original callback is invoked with a result resolves correctly; one whose callback is invoked with an Error produces a genuinely rejected Promise, catchable with .catch() or try/catch.
  • It requires the target function to follow the exact error-first, callback-last convention β€” a function with a different callback signature (callback not last, multiple non-error result arguments in a non-standard shape) will not promisify correctly without a custom wrapper.
  • Many of Node's own built-in APIs already ship a .promises variant (e.g. fs.promises) or accept util.promisify directly (util.promisify(fs.readFile)) β€” a good answer knows both paths exist and that the built-in .promises versions are generally preferred where available, since they are maintained directly rather than wrapped.
  • This directly enables using async/await with a legacy callback API without waiting for (or forking) that library to add native Promise support β€” the wrapping happens once, typically in a small compatibility module, not at every call site.
  • A precise answer distinguishes util.promisify (a general, one-function-at-a-time conversion utility) from util.callbackify (its inverse β€” wrapping an async function to expose an error-first callback API instead), which solves the opposite integration problem.

Clarifying questions expected:

  • "Does the callback-style function actually follow the standard error-first, callback-last convention?" β€” util.promisify assumes exactly that shape.
  • "Is there already a built-in .promises variant available for this specific API?" β€” often preferable to wrapping manually.

Code / implementation expected: Yes β€” demonstrating both the success path and a genuine rejection path from a promisified function is the concrete, convincing part of this answer.

  • utilities
  • promises
  • async

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

6 min read Β· 9 sections

Read the code

JavaScriptutil.promisify verified for both the success path and a genuine rejection path
const util = require("util");

function legacyAdd(a, b, cb) { setTimeout(() => cb(null, a + b), 10); }
const addAsync = util.promisify(legacyAdd);
addAsync(2, 3).then((r) => console.log("promisified result:", r));
// promisified result: 5

function legacyFail(cb) { setTimeout(() => cb(new Error("legacy failure")), 10); }
util.promisify(legacyFail)().catch((e) => console.log("promisified rejection:", e.message));
// promisified rejection: legacy failure

// A method needing "this" binding before promisifying:
// const readAsync = util.promisify(client.legacyRead.bind(client));

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 21 of 152 in Node.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Node.js questions
Up nextEasy

What is the difference between blocking and non-blocking I/O in Node.js?

Continue