Spring Fresh Sale! - Up To 67% OFF BDIX Hosting + Free Domain
Node.js

Async/Await vs Promises vs Callbacks: Patterns and Pitfalls in Node.js

Callbacks, Promises and async/await are three generations of the same idea: handling work that finishes later without blocking Node.js. Modern code leans on async/await, but knowing all three — and their traps — makes you a far more effective Node developer.

Callbacks: the original

A callback is a function you pass in to run when the work completes. It works, but nesting several leads to "callback hell" — deeply indented, hard-to-read code. The classic convention is error-first: (err, result) => {}.

Promises: composable and cleaner

A Promise represents a future value you can chain with .then() and handle errors with .catch(). Promises flatten nesting and compose well — Promise.all() runs several in parallel and waits for all.

Async/await: Promises that read like sync code

Async/await is syntax on top of Promises. It lets you write asynchronous logic that reads top-to-bottom, with normal try/catch for errors:

async function load() { try { const data = await getData(); return data; } catch (err) { /* handle */ } }

Common pitfalls

  • Forgetting await — you get a pending Promise instead of the value.
  • Sequential when you meant parallel — awaiting in a loop runs one at a time; use Promise.all for independent tasks.
  • Unhandled rejections — always catch; see handling unhandled rejections.
  • Mixing styles — wrap old callback APIs with util.promisify to keep code consistent.

Frequently asked questions

Which should I use in new code?

Async/await, in almost every case — it is the clearest and least error-prone. Drop to raw Promises for combinators like Promise.all, and use callbacks only when a library forces them.

How do I run async tasks in parallel?

Start them without awaiting individually, then await Promise.all([...]). Awaiting each in sequence inside a loop is the number-one cause of slow async code.

Does async/await block the event loop?

No — await yields while waiting, so other work proceeds. It only looks synchronous; it does not block. Blocking comes from synchronous CPU work, not from awaiting.

Was this article helpful?