Node.js – callback-based functions into promise-based functions

In Node.js, the util.promisify() function is a powerful tool that allows you to convert callback-based functions into promise-based functions. This conversion enables you to use promise chaining and async/await with APIs that originally relied on callbacks.

Let’s dive into how it works:

Callback-Based Functions:

  • Consider an example using the fs.readFile() function, which reads a file and invokes a callback when done:
const fs = require('fs');

fs.readFile('./package.json', (err, buf) => {
  if (err) {
    console.error('Error reading file:', err);
    return;
  }
  const obj = JSON.parse(buf.toString('utf8'));
  console.log('Project name:', obj.name); // 'masteringjs.io'
});
  • The traditional callback approach can lead to callback hell when dealing with multiple asynchronous operations.

Using util.promisify():

  • util.promisify() transforms the fs.readFile() function into a promise-based function:
const util = require('util');
const readFile = util.promisify(fs.readFile);

// Now you can use `readFile()` with `await`!
try {
  const buf = await readFile('./package.json');
  const obj = JSON.parse(buf.toString('utf8'));
  console.log('Project name:', obj.name); // 'masteringjs.io'
} catch (err) {
  console.error('Error reading file:', err);
}
  • The readFile() function now returns a promise, allowing you to handle asynchronous file reading more elegantly.

How Does util.promisify() Work?:

  • Under the hood, util.promisify() adds an extra callback parameter to the original function.
  • This callback resolves or rejects the promise returned by the promisified function.
  • Here’s a simplified custom implementation of util.promisify()
function promisify(fn) {
  return function (...args) {
    return new Promise((resolve, reject) => {
      fn.apply(this, [...args, (err, res) => {
        if (err) {
          reject(err);
        } else {
          resolve(res);
        }
      }]);
    });
  };
}

const readFile = promisify(fs.readFile);
// Now you can use `readFile()` with `await`!

Important Considerations:

  • Ensure that the original function supports the additional callback argument.
  • Be aware of losing context (i.e., incorrect this value) when using promisified functions.

Remember, util.promisify() simplifies asynchronous code, making it more readable and manageable.