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 withresultor rejecting witherr.- 📌 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
Errorproduces a genuinely rejected Promise, catchable with.catch()ortry/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
.promisesvariant (e.g.fs.promises) or acceptutil.promisifydirectly (util.promisify(fs.readFile)) — a good answer knows both paths exist and that the built-in.promisesversions are generally preferred where available, since they are maintained directly rather than wrapped. - This directly enables using
async/awaitwith 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) fromutil.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.promisifyassumes exactly that shape. - "Is there already a built-in
.promisesvariant 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.