3 Ways to Convert Callbacks to Promises in Node.js

Updated: December 28, 2023 By: Guest Contributor Post a comment

Some different ways to convert callbacks to promises in Node.js applications.

Utilizing Promisify Utility

The util.promisify function is a built-in utility in Node.js that converts a callback-based function to a Promise-based one. This works great for functions that follow the Node.js callback style, which have a signature of (err, result) =>.

  • Import the util module using require('util').
  • Identify the callback-based function you want to convert.
  • Use util.promisify to create a Promisified version of the function.
  • Invoke the new function and use .then() and .catch() to handle the resolved value or rejection.
const util = require('util');
const fs = require('fs');
const readFileAsync = util.promisify(fs.readFile);

readFileAsync('example.txt', 'utf8').then(contents => {
    console.log(contents);
}).catch(error => {
    console.error('Error reading file:', error);
});

Pros: Easy to implement; built-in Node.js utility; clean, promise-style code.

Cons: Limited to Node callback pattern; not suitable for converting custom callback functions.

Creating New Promises

You can manually wrap callback-based functions with a new Promise instance, which gives you full control over how the callback arguments are handled.

  • Create a new Promise instance using new Promise().
  • Execute the callback-based function within the Promise executor.
  • Resolve or reject the Promise based on the callback result.
  • Invoke the new Promise-based function and handle responses with .then() and .catch().
const fs = require('fs');

function readFileAsync(filePath, encoding) {
    return new Promise((resolve, reject) => {
        fs.readFile(filePath, encoding, (err, data) => {
            if (err) reject(err);
            else resolve(data);
        });
    });
}

readFileAsync('example.txt', 'utf8').then(contents => {
    console.log(contents);
}).catch(error => {
    console.error('Error reading file:', error);
});

Pros: Flexible for custom callback functions; full control over the promise resolution.

Cons: More verbose; scope for errors in handling callback arguments.

Using Async/Await with Promises

Async/Await syntax in conjunction with Promises can simplify asynchronous code, making it easier to read and maintain. This approach assumes the functions being called return Promises.

  • Ensure the function you are calling returns a Promise.
  • Use async to declare an asynchronous function.
  • Within an async function, call the promise-returning functions with await, and handle errors with try/catch blocks.
const util = require('util');
const fs = require('fs');
const readFileAsync = util.promisify(fs.readFile);

async function readExampleFile() {
    try {
        const contents = await readFileAsync('example.txt', 'utf8');
        console.log(contents);
    } catch (error) {
        console.error('Error reading file:', error);
    }
}

readExampleFile();

Pros: Syntactic sugar makes code easier to read and write; resembles synchronous code.

Cons: Requires understanding of async functions and await; error handling diverges from traditional Promise chains.

Conclusion

Converting callbacks to promises in Node.js can significantly improve code readability and error handling. The util.promisify utility is convenient for Node.js style callbacks, while wrapping callbacks with a new Promise provides more flexibility. Furthermore, the async/await syntax offers a synchronous feel to asynchronous operations, enhancing code clarity. Choosing the right approach often depends on the specific use case and the callback function’s behavior.