How to convert callback to promise-based functions.
Posted
Updated
Europe’s developer-focused job platform
Let companies apply to you
Developer-focused, salary and tech stack upfront.
Just one profile, no job applications!
Promises allow to handle the results of asynchronous code, like callbacks.
Though, unlike callbacks, the async code with promises is easier to read, maintain, and reason about.
They make writing asynchronous code easier and are an improvement to the callback pattern.
If you want to see for yourself, feel free to search for callback hell
in your favorite search engine.
This article is based on Node v16.14.0.
💰 The Pragmatic Programmer: journey to mastery. 💰 One of the best books in software development, sold over 200,000 times.
Learn more about Promises in the article Understanding Promises in Node.js.
Node.js has a built-in util package. This util package includes utility functions.
One of these is the promisfy()
function that converts callback-based to promise-based functions.
With the help of this util function we are able to use promise chaining and async/await
with callback-based APIs.
Let's try to read the package.json
with the built-in fs
package.
Create project folder and init with npm init -y
to auto-generate a package.json
.
mkdir node-promisfy
cd node-promisfy
npm init -y
Create a index.js
file.
touch index.js
Copy the callback-based test code.
const fs = require('fs');
fs.readFile('./package.json', function callback(err, data) {
const package = JSON.parse(data.toString('utf8'));
console.log(package.name);
});
Try to run this code with node index.js
and the output should be node-promisify
.
Now, let's convert this fs.readFile
function to be promise-based instead of callback-based.
const fs = require('fs');
const util = require('util');
// Convert `fs.readFile()` into a function that takes the
// same parameters but returns a promise.
const readFile = util.promisify(fs.readFile);
readFile('./package.json')
.then(response => {
const package = JSON.parse(response.toString('utf8'));
console.log(package.name);
})
.catch(err => console.log(err));
Or, equivalently to .then
handler, using an async function
with logging the uid
of the directory owner.
const util = require('util');
const fs = require('fs');
const stat = util.promisify(fs.stat);
async function callStat() {
const stats = await stat('.');
console.log(`This directory is owned by ${stats.uid}`);
}
callStat();
Since Node.js version 10+, the fs library has built-in support for Promises.
The fs.promises
API provides an alternative set of asynchronous file system methods that return Promise objects rather than using callbacks.
You can access this API via require('fs').promises
or require('fs/promises')
.
The example for reading the package.json
file, is much cleaner and easier to read.
const fsp = require('fs').promises;
fsp
.readFile('./package.json')
.then(response => {
const package = JSON.parse(response.toString('utf8'));
console.log(package.name);
})
.catch(err => console.log(err));
promisify()
assumes that original is a function taking a callback as its final argument in all cases.
If original is not a function, an error will be thrown by promisify()
.
If original is a function but its last argument is not an error-first callback, it will still be passed an error-first callback as its last argument.
Using promisify()
on class methods or other methods that use this
may not work as expected.
Special handling (binding) for that case is required, see example below:
const util = require('util');
class Foo {
constructor() {
this.a = 42;
}
bar(callback) {
callback(null, this.a);
}
}
const foo = new Foo();
const naiveBar = util.promisify(foo.bar);
// TypeError: Cannot read property 'a' of undefined
// naiveBar().then(a => console.log(a));
naiveBar.call(foo).then(a => console.log(a)); // '42'
const bindBar = naiveBar.bind(foo);
Using the util.promisify.custom
symbol one can override the return value of util.promisify()
, see more in the Node.js docs:
const util = require('util');
function doSomething(foo, callback) {
// ...
}
doSomething[util.promisify.custom] = foo => {
return getPromiseSomehow();
};
const promisified = util.promisify(doSomething);
console.log(promisified === doSomething[util.promisify.custom]);
// prints 'true'
This can be useful, if the original function does not follow the error-first callback, so if the function takes (foo, onSuccessCallback, onErrorCallback)
instead of onErrorCallback
first.
doSomething[util.promisify.custom] = foo => {
return new Promise((resolve, reject) => {
doSomething(foo, resolve, reject);
});
};
If promisify.custom
is defined but is not a function, promisify()
will throw an error.
util.promisify
takes a function following error-first callback style (err, value) => ...
and returns a version that returns promises.promisify
can be required via the util
package and is built-in.fs
module has a fs.promises
API which returns Promise
objects, since Node.js Version 10.promisify()
on class methods or other methods which implement this
requires special handling.Thanks for reading and if you have any questions, use the comment function or send me a message @mariokandut.
If you want to know more about Node, have a look at these Node Tutorials.
References (and Big thanks):
Node.js promisify(), Node.js fs.promises, Flavio, MasteringJS
Never miss an article.