DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Async/Await vs Promises vs Callbacks: JavaScript Async Patterns Explained

Async/Await vs Promises vs Callbacks: JavaScript Async Patterns Explained

JavaScript has three async patterns. Understanding when each applies prevents bugs and makes code readable.

Callbacks: The Origin

// Node.js-style callback (error-first)
fs.readFile('/path/to/file', 'utf8', (err, data) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  console.log(data);
});

// Callback hell — the reason Promises were invented
getUser(userId, (err, user) => {
  getOrders(user.id, (err, orders) => {
    getProduct(orders[0].productId, (err, product) => {
      // Deeply nested, error handling everywhere
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

Promises: Chainable Async

// Promise chain — flatter than callbacks
getUser(userId)
  .then(user => getOrders(user.id))
  .then(orders => getProduct(orders[0].productId))
  .then(product => console.log(product))
  .catch(err => console.error(err));

// Creating a Promise
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

// Promisifying callbacks
const readFile = (path) => new Promise((resolve, reject) => {
  fs.readFile(path, 'utf8', (err, data) => err ? reject(err) : resolve(data));
});
// Or use util.promisify(fs.readFile)
Enter fullscreen mode Exit fullscreen mode

Async/Await: The Modern Standard

// Same logic, reads like synchronous code
async function processOrder(userId) {
  try {
    const user = await getUser(userId);
    const orders = await getOrders(user.id);
    const product = await getProduct(orders[0].productId);
    return product;
  } catch (err) {
    console.error('Failed:', err);
    throw err;
  }
}
Enter fullscreen mode Exit fullscreen mode

Parallel vs Sequential

// Sequential: each awaits the previous — slow
const user = await getUser(userId);     // 100ms
const orders = await getOrders(userId); // +100ms
const prefs = await getPrefs(userId);   // +100ms = 300ms total

// Parallel: all start simultaneously — fast
const [user, orders, prefs] = await Promise.all([
  getUser(userId),    // }
  getOrders(userId),  // } all start at once
  getPrefs(userId),   // } ~100ms total
]);
Enter fullscreen mode Exit fullscreen mode

Promise.allSettled: When Some May Fail

// Promise.all rejects if ANY promise rejects
// Promise.allSettled waits for all, reports success/failure per item
const results = await Promise.allSettled([
  fetchPrimaryData(),
  fetchOptionalData(), // OK if this fails
]);

results.forEach(result => {
  if (result.status === 'fulfilled') console.log(result.value);
  if (result.status === 'rejected') console.warn(result.reason);
});
Enter fullscreen mode Exit fullscreen mode

Common Mistakes

// Forgetting await — returns Promise, not value
const user = getUser(userId); // Bug: user is a Promise
console.log(user.name);       // undefined

// Unhandled rejection (crashes Node.js)
someAsyncFn(); // Missing await AND missing .catch()

// Fix: always await or chain .catch()
await someAsyncFn();
someAsyncFn().catch(handleError);
Enter fullscreen mode Exit fullscreen mode

TypeScript catches many async mistakes at compile time — the strict setup in the AI SaaS Starter Kit has strictNullChecks and proper async error handling configured from day one.

Top comments (0)