All questions

Promisify II

Premium

Promisify II

A sequel to promisify. The v1 wrapper only handles (err, value) — but real Node APIs call back with more than one value (child_process.exec(err, stdout, stderr), fs.read(err, bytesRead, buffer)), and sometimes you want to reshape the resolved value. Extend the wrapper with an options object: an opt-in for multi-value resolution, and a custom hook to override the result entirely.

You're implementing promisifyII(original, options?). With no options, behaviour is identical to v1. With options, two hooks are available — and transform always wins over multiArgs when both are passed.

Signature

type Options = {
  multiArgs?: boolean;
  // transform receives the raw callback arguments and returns the resolve value.
  // If it throws, the promise rejects with the thrown error.
  transform?: (err: unknown, ...args: unknown[]) => unknown;
};

function promisifyII(
  original: (...args: any[]) => void,
  options?: Options,
): (...args: any[]) => Promise<any>;

Examples

// 1. No options — behaves exactly like v1.
const readFile = (path, cb) => cb(null, 'contents');
const readP = promisifyII(readFile);
await readP('./hi.txt'); // 'contents'
// 2. multiArgs — resolve with the whole result array.
const stat = (cb) => cb(null, 1024, 'file');
const statP = promisifyII(stat, { multiArgs: true });
await statP(); // [1024, 'file']
// 3. transform — pick a specific shape out of a multi-arg callback.
const exec = (cmd, cb) => cb(null, 'stdout-text', 'stderr-text');
const execP = promisifyII(exec, {
  transform: (err, stdout, stderr) => ({ stdout, stderr }),
});
await execP('ls'); // { stdout: 'stdout-text', stderr: 'stderr-text' }

Notes

  • Precedence. If both multiArgs and transform are provided, transform wins — multiArgs is ignored. Document this; don't fall through.
  • Transform owns success and failure. transform is invoked even when err is truthy. It decides what the promise resolves with; if it throws, the promise rejects with that thrown error.
  • multiArgs is always an array. Even for a single-value callback, multiArgs: true resolves with [value] — consistency beats cleverness.
  • No options object is fine. promisifyII(fn) must work identically to v1: resolve with the second arg, reject on the first.
  • Preserve this. Same rule as v1 — use a regular function for the wrapper and .call(this, ...) to forward into the original.
  • Out of scope. util.promisify.custom, an inverse callbackify, and bulk promisifyAll(obj) are interesting follow-ups but not part of this question.

Unlock the solution & editor

  • Runnable editor + tests

    Solve in the browser with instant Jest feedback.

  • Detailed solutions

    Walkthroughs, edge cases, and complexity notes.

  • Multi-framework variants

    React, Vue, Vanilla, Angular — same question, different stacks.

Upgrade to Premium