DEV Community

Mario
Mario

Posted on • Originally published at mariokandut.com on

Callback to promise-based functions

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.

Learn more about Promises in the article Understanding Promises in Node.js.

What is util.promisify()?

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
Enter fullscreen mode Exit fullscreen mode

Create a index.js file.

touch index.js
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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();
Enter fullscreen mode Exit fullscreen mode

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));
Enter fullscreen mode Exit fullscreen mode

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

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);
Enter fullscreen mode Exit fullscreen mode

Custom promisified functions

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'
Enter fullscreen mode Exit fullscreen mode

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);
  });
};
Enter fullscreen mode Exit fullscreen mode

If promisify.custom is defined but is not a function, promisify() will throw an error.

TL;DR

  • 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.
  • The built-in file-system fs module has a fs.promises API which returns Promise objects, since Node.js Version 10.
  • Using 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

Top comments (0)