Problem
You want to create a generic function to promisify asynchronous functions.
Ingredients
- closures
- rest parameters
- spread operator
- promises
- arrow functions
Directions
-
Given: an asynchronous function …
const add = (x, y, z, callback) => { setTimeout(() => { callback( // Remember: null, // First argument: the error x + y + z // Second argument: the result ); }, 5000); }
-
… that we call like this …
add(2,3,4, (error, result) => { if(error) { console.error(error); } else { console.log(result)); } }
-
… but what we really want is this:
const promisifiedAdd = promisify(add); promisifiedAdd(2,3,4) .then(result => console.log(result)) .catch(error => console.error(error));
-
So let’s get started! First create a function
promisify()
that takes a function …function promisify(fn) { ... }
-
… and returns another function (that uses rest parameters).
function promisify(fn) { return function (...args) { ... } }
-
Let this function return a
Promise
object.function promisify(fn) { return function (...args) { return new Promise((resolve, reject) => { ... }); } }
-
And inside the callback of the
Promise
object, call the original function, passing the rest parameters to it (using the spread operator) and a callback function.function promisify(fn) { return function (...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { ... }); }); } }
-
Inside the callback function of the
Promise
object check if there is an error. If so, then call the methodreject()
passing theobject
object as argument.function promisify(fn) { return function (...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { if(error) { return reject(error); } }); }); } }
-
Otherwise call the method
resolve()
passing theresult
object as argument.function promisify(fn) { return function (...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { if(error) { return reject(error); } else { return resolve(result); } }); }); } }
-
Optimization: combine the last two steps.
function promisify(fn) { return function (...args) { return new Promise((resolve, reject) => { fn(...args, (error, result) => { return error ? reject(error) : resolve(result); }); }); } }
-
Optimization: replace
function
-style functions by arrow functions.const promisify = (fn) => { return (...args) => { return new Promise((resolve, reject) => { fn(...args, (error, result) => { return error ? reject(error) : resolve(result); }); }); } }
-
Optimization: replace the
return
keywords and the brackets where possible.const promisify = fn => // take the original function (...args) => // return another function new Promise((resolve, reject) => // that returns a promise fn(...args, (error, result) => // which calls the original error ? reject(error) : resolve(result))); // and handles the results
-
Voilá, a perfect generic function for converting asynchronous functions to promises.
const promisify = fn => (...args) => new Promise((resolve, reject) => fn(...args, (error, result) => error ? reject(error) : resolve(result))); const add = (x, y, z, callback) => { setTimeout(() => { callback(null, x + y + z); }, 5000); } const promisifiedAdd = promisify(add); promisifiedAdd(2,3,4) .then(result => console.log(result)) .catch(error => console.error(error));
Notes
- Only works for functions where the last parameter is the callback.
- Doesn’t check if the passed argument is a function.
- Doesn’t check if the passed argument is a promise.
- In the shown variant it only works if the promisified function does not need access to the execution context via
this
.
Alternative recipes
- Use a promisify library.
- Directly write all your code using promises.