Introduction
In node.js there is the convention of error first callbacks, which get passed to nearly every asynchronous function and get called on completion or when an error occurs. Some developers (like me) rather work with promises, since they tend to be more readable. Therefore there are libraries, which provide some or even all the node.js functions in a way, that they don't get the callback passed, but return simply promises, which can be used for flow control.
Here's a method how you can simply convert such asynchronous functions into promise returning ones.
The helper function
The widely spread convention of passing the callback as the last parameter comes in handy here. It allows us to create a new function, which accepts the original asynchronous function and returns a new one which calls it and returns a promise.
function promisify (fn) {
return function () {
// copy passed arguments to a new array
const args = Array.prototype.slice.call(arguments);
return new Promise(function (resolve, reject) {
// add a callback to the arguments, which resolves the promise with the result
args.push(function (err, data) {
if (err) {
reject(err);
} else {
resolve(data);
}
});
// call the original function with our callback flavoured arguments
fn.apply(null, args);
});
};
};
Our promisify function gets nothing but the original asynchronous function. It returns a new function, which can be used later on. This function just takes the passed parameters, adds an error first callback to the parameters, calls the original function with these parameters and returns a promise. Our anonymous callback then resolves or rejects the promise.
Example
Let's take the fs.writeFile function of node.js for our example (and ignore, that there's also a synced one).
Actually you would use it that way:
const fs = require('fs');
fs.writeFile('filename.txt', 'some awesome content', function (err) {
if (err) {
console.log(err);
} else {
console.log('the file has been written');
}
});
In this small example, we would call the function and in the callback we would do nothing more than logging a message onto the console. But like you may have experienced, this way often leads to a so called callback hell. This happens when you want to create a series of asynchronous tasks, where each has to wait for the previous one to complete.
Let's create a simple example with two asynchronous calls:
const fs = require('fs');
fs.readFile('somefile.txt', function (err, data) {
if (err) {
console.log(err);
} else {
fs.writeFile('newfile.txt', data, function (err) {
if (err) {
console.log(err);
} else {
console.log('the file has been written');
}
});
}
});
Now you can imagine, how ugly it can get, when you have multiple asynchronous tasks depending on each other. With our promisify helper function, we can write it a bit more elegantly. Let's look at an example:
const fs = require('fs');
const pReadFile = promisify(fs.readFile);
const pWriteFile = promisify(fs.writeFile);
pReadFile('somefile.txt')
.then(function (data) {
return pWriteFile('newfile.txt', data);
})
.then(function () {
console.log('the file has been written');
})
.catch(err => console.log(err);
Here you can see, how easy it is to chain asynchronous functions together. Furthermore this allows us to create only one point of error handling, when one of the functions return an error.
Alternative
The most famous alternative is probably async. This is a library with far more helper functions for the most asynchronous use cases. For my taste, I like the promise approach more because it reads cleaner, especially when making use of the ES6 arrow functions.