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.
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>;
// 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' }
multiArgs and transform are provided, transform wins — multiArgs is ignored. Document this; don't fall through.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: true resolves with [value] — consistency beats cleverness.promisifyII(fn) must work identically to v1: resolve with the second arg, reject on the first.this. Same rule as v1 — use a regular function for the wrapper and .call(this, ...) to forward into the original.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.