[Node.js] How to Use Promises in a Callback-based Codebase

Gaurang PansareGaurang Pansare
3 min read

Hi! I'm Gaurang. I’ve worked extensively with Node.js in the past, and during that time, I spent a lot of effort wrangling with different async patterns — callbacks, promises, and async/await. This article is part of my series, "My guide to getting through the maze of callbacks, promises, and async-await", where I share lessons learned and best practices for writing asynchronous code in Node.js.

This article assumes you’re familiar with basic Node.js concepts, especially asynchronous programming constructs like callbacks, promises, and async/await.


Converting a Promise-based Function to Use Callbacks

Often, you’ll find yourself working in older codebases where asynchronous functions are written using the callback pattern. Node.js also offers a built-in way to convert a Promise-returning function into a callback-based one using util.callbackify().

Personally, I believe we should avoid writing new callbacks whenever possible. Even in a callback-heavy codebase, it's often worth introducing promises or async/await for better readability and maintainability.
I’ve spent years digging through callback-infested codebases, and I’ll say it plainly: callbacks are obsolete. They had their time, but that time is over. Stop writing them. Let them die.

Use this only when absolutely necessary — like when you're deep in a legacy callback jungle and need to drop in a Promise-based function without rewriting everything around it.

Prefer Then-chaining over nesting

It's common to see people misuse promises by nesting .then() calls instead of chaining them. This leads to "Promise hell", which is structurally similar to "callback hell."

Callbacks are difficult to work with primarily because of their deeply nested structure. Promises were introduced to address this by enabling a more linear, manageable code flow.

Of course, code with then-chaining may not be as readable and intuitive as synchronous code or code written with async-await. But it is more manageable than code with nested thens.

For a deeper dive into why Promise hell is best avoided — and how to write cleaner promise chains — check out these excellent articles:

Keep in mind that it’s not just .then() chains you need to keep clean — don’t nest callback-based functions inside a .then() either. This is a common mistake that defeats the purpose of using Promises in the first place.

If you’re already using Promises, don’t go back to callbacks mid-way. Promises are designed to give you a linear, manageable control flow — and the moment you reintroduce nested callbacks, you throw that benefit away. Whether you’re nesting callbacks or .then() blocks, the result is the same: harder-to-read code.

Instead, convert the callback-based function into a Promise using any of the methods discussed in my previous article “Using callback-based functions when the rest of the code uses Promises“ and keep your chain linear.

Notice that readFile2CB is a callback-based function. If you nest it (as shown in the bad-code example), any code that relies on content2 or results must go inside that nested block. Over time, this leads to deeply indented, harder-to-read code — exactly the kind of mess Promises were created to prevent.

Once you start using Promises, commit to the pattern. Nesting defeats the purpose. Whether you’re nesting .then() blocks or stuffing callbacks inside them, you’re trading away clarity and maintainability — the very reasons Promises exist.


If you're stuck in a callback-heavy codebase, I feel your pain. Use these patterns sparingly, but push for modern async functions where you can — your future self (and your teammates) will thank you.

I've put a lot of work into this article. And I hope that it has been helpful. If you have any questions, please feel free to leave a comment below. I'd really appreciate it if you could like and share the article! 😊

10
Subscribe to my newsletter

Read articles from Gaurang Pansare directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Gaurang Pansare
Gaurang Pansare

I am a passionate backend developer with expertise in Node.js and JavaScript. I have experience in developing scalable and efficient back-end services, APIs, and microservices using Node.js. My work experience includes working with various companies, startups, and individuals in developing their backend architecture and solving complex issues. My ability to learn and adapt to new technologies quickly positions me as an asset to any organization. I am passionate about creating software that solves complex problems and enhances the user experience. If you are seeking a highly skilled and motivated backend developer, I would welcome the opportunity to work with you.