Callback to promise-based functions
© https://nodejs.org/en/

Callback to promise-based functions

How to convert callback to promise-based functions.

By Mario Kandut

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.

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

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

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);

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'

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.

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

More node articles:

How to create a web server in Node.js

How to dynamically load ESM in CJS

How to convert a CJS module to an ESM

How to create a CJS module

How to stream to an HTTP response

How to handle binary data in Node.js?

How to use streams to ETL data?

How to connect streams with pipeline?

How to handle stream errors?

How to connect streams with pipe?

What Is a Node.js Stream?

Handling Errors in Node (asynchronous)

Handling Errors in Node.js (synchronous)

Introduction to errors in Node.js

Callback to promise-based functions

ETL: Load Data to Destination with Node.js

ETL: Transform Data with Node.js

ETL: Extract Data with Node.js

Event Emitters in Node.js

How to set up SSL locally with Node.js?

How to use async/await in Node.js

What is an API proxy?

How to make an API request in Node.js?

How does the Event Loop work in Node.js

How to wait for multiple Promises?

How to organize Node.js code

Understanding Promises in Node.js

How does the Node.js module system work?

Set up and test a .env file in Node

How to Use Environment Variables in Node

How to clean up node modules?

Restart a Node.js app automatically

How to update a Node dependency - NPM?

What are NPM scripts?

How to uninstall npm packages?

How to install npm packages?

How to create a package.json file?

What Is the Node.js ETL Pipeline?

What is data brokering in Node.js?

How to read and write JSON Files with Node.js?

What is package-lock.json?

How to install Node.js locally with nvm?

How to update Node.js?

How to check unused npm packages?

What is the Node.js fs module?

What is Semantic versioning?

The Basics of Package.json explained

How to patch an NPM dependency

What is NPM audit?

Beginner`s guide to NPM

Getting started with Node.js

Scroll to top ↑