Promisification
“Promisification” is a bulky word for a small idea: take a function that reports its result through a callback and turn it into one that hands you a promise instead.
You reach for this transformation constantly. A large share of older APIs and libraries are built around callbacks, but promises slot into async/await and chaining far more cleanly. So you wrap the callback-style function once and use the promise-style version everywhere after.
Before the mechanics, picture the shift in shape. The same operation, two different ways of delivering the answer:
A concrete starting point
Take an error-first callback like loadImage(src, callback), in the same spirit as the ones from the chapter on callbacks.
function loadImage(src, callback) {
let img = document.createElement('img');
img.src = src;
img.onload = () => callback(null, img);
img.onerror = () => callback(new Error(`Image load error for ${src}`));
document.body.append(img);
}
// usage:
// loadImage('path/photo.png', (err, img) => {...})
The function downloads an image from src. When the download fails it calls callback(err); when it succeeds it calls callback(null, img). Passing the error first, and null in its place on success, is a long-standing convention — the error-first callback. You’ve seen it before, and Node.js built its entire standard library around it.
Now let’s convert it to a promise.
Wrapping one function by hand
The goal is a new function, loadImagePromise(src), that does the same job but returns a promise. You give it only src — no callback — and you get back a promise that resolves with img on success and rejects with the error on failure.
let loadImagePromise = function(src) {
return new Promise((resolve, reject) => {
loadImage(src, (err, img) => {
if (err) reject(err);
else resolve(img);
});
});
};
// usage:
// loadImagePromise('path/photo.png').then(...)
The new function is a thin wrapper. It creates a promise, calls the original loadImage inside, and supplies its own callback — a small adapter whose only job is to translate the callback’s two outcomes into resolve or reject.
Here is the flow of that adapter, from the moment you call the wrapper to the moment the promise settles:
loadImagePromise now drops straight into promise-based code. Once you prefer promises over callbacks — and the chapters ahead give you more reasons to — you use this version.
A reusable helper
Doing that by hand for every function gets old fast. In practice you promisify many functions, so it pays to extract the pattern into a helper.
Call it promisify(f): it takes a callback-based function f and returns a promise-based wrapper.
function promisify(f) {
return function (...args) { // return a wrapper-function (*)
return new Promise((resolve, reject) => {
function callback(err, result) { // our custom callback for f (**)
if (err) {
reject(err);
} else {
resolve(result);
}
}
args.push(callback); // append our custom callback to the end of f arguments
f.call(this, ...args); // call the original function
});
};
}
// usage:
let loadImagePromise = promisify(loadImage);
loadImagePromise(...).then(...);
Denser than the hand-written version, but it’s the exact same idea, generalized. Walking it in layers:
promisify(f)returns a wrapper function(*). That wrapper is what you actually call later.- When you call the wrapper with some arguments, it builds a promise and defines
callback(**)— the same little adapter as before. - It pushes
callbackonto the end of the argument list, then calls the originalfwith the full set. Sofreceives your real arguments plus the adapter as its final callback.
This version assumes the original f calls its callback with exactly two arguments, (err, result). That covers the common case, and the adapter fits it perfectly.
Callbacks with several results
What if f calls back with more than one result — callback(err, res1, res2, ...)? The two-argument version would quietly hand you only res1 and throw the rest away.
Let’s extend the helper. We add a second parameter:
promisify(f)behaves exactly like the version above — resolves with a single value.promisify(f, true)resolves with an array of all the callback’s result arguments. That’s the mode for many-argument callbacks.
// promisify(f, true) to get array of results
function promisify(f, manyArgs = false) {
return function (...args) {
return new Promise((resolve, reject) => {
function callback(err, ...results) { // our custom callback for f
if (err) {
reject(err);
} else {
// resolve with all callback results if manyArgs is specified
resolve(manyArgs ? results : results[0]);
}
}
args.push(callback);
f.call(this, ...args);
});
};
}
// usage:
f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...);
The change is small. The adapter now gathers everything after err into a results array using rest parameters. On success it resolves with the whole array when manyArgs is truthy, or just results[0] otherwise — which reproduces the single-value behavior.
When the helper doesn’t fit
Some callbacks don’t follow the error-first shape at all. A function might call callback(result) with no error slot, or split success and failure into two separate callbacks. For those exotic signatures, skip the generic helper and write the wrapper by hand — it’s only a few lines, and you control exactly how the arguments map to resolve and reject.
You rarely need to write promisify yourself in production. Libraries offer more flexible versions, such as es6-promisify. And Node.js ships a built-in util.promisify for exactly this job.