How do you convert a callback-based API to a promise?
You wrap the call in a promise executor and resolve or reject inside the callback. The executor function receives the two functions that control the promise, so the callback API's result becomes the promise's result.
// Wrap a callback-style function in a promise.
function delay(ms, value) {
return new Promise((resolve) => {
setTimeout(() => resolve(value), ms);
});
}
delay(50, "done").then((value) => console.log(value));
// The same thing written with the callback API.
function delayCallback(ms, value, callback) {
setTimeout(() => callback(null, value), ms);
}
delayCallback(50, "done too", (err, value) => {
if (err) {
console.log("failed:", err.message);
return;
}
console.log(value);
});Notice the shape of the error-first callback. The error is the first argument and the value is the second, and a null error means the operation succeeded. The promise wrapper inverts that: you call resolve with the value on success and reject with an error on failure. That inversion is what makes promises read better, because a successful path has to be stated explicitly rather than being implied by an absent error.
There is a generic version of this that is worth writing once and reusing. The promisify helper takes a function that follows the error-first convention and returns a function that returns a promise. It works for any number of arguments because the extra arguments are spread before the callback.
Two cautions. The callback must be called exactly once, because a promise settles only the first time. And if the underlying API can throw synchronously, wrap the call in a try and catch inside the executor and reject from there, otherwise the promise will hang rather than rejecting.