Sling Academy
Home/Node.js/3 Ways to Convert Callbacks to Promises in Node.js

3 Ways to Convert Callbacks to Promises in Node.js

Last updated: December 28, 2023

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.

Next Article: How to Build a GraphQL API with Node.js and Apollo Server

Previous Article: How to Use Child Processes in Node.js for CPU-Intensive Tasks

Series: Node.js Intermediate Tutorials

Node.js

You May Also Like

  • NestJS: How to create cursor-based pagination (2 examples)
  • Cursor-Based Pagination in SequelizeJS: Practical Examples
  • MongooseJS: Cursor-Based Pagination Examples
  • Node.js: How to get location from IP address (3 approaches)
  • SequelizeJS: How to reset auto-increment ID after deleting records
  • SequelizeJS: Grouping Results by Multiple Columns
  • NestJS: Using Faker.js to populate database (for testing)
  • NodeJS: Search and download images by keyword from Unsplash API
  • NestJS: Generate N random users using Faker.js
  • Sequelize Upsert: How to insert or update a record in one query
  • NodeJS: Declaring types when using dotenv with TypeScript
  • Using ExpressJS and Multer with TypeScript
  • NodeJS: Link to static assets (JS, CSS) in Pug templates
  • NodeJS: How to use mixins in Pug templates
  • NodeJS: Displaying images and links in Pug templates
  • ExpressJS + Pug: How to use loops to render array data
  • ExpressJS: Using MORGAN to Log HTTP Requests
  • NodeJS: Using express-fileupload to simply upload files
  • ExpressJS: How to render JSON in Pug templates