How does async/await work, and how do you convert callback-based APIs to promises?

3 minintermediatenodejsasync-awaitpromisifypromiseserror-handling

Quick Answer

async/await is syntactic sugar over Promises: an async function returns a Promise, and await pauses it until a promise settles, using try/catch for errors. Convert Node-style callback APIs with util.promisify, or use the built-in promise variants like fs.promises.

Detailed Answer

Answer:

async/await lets you write promise-based code that reads sequentially:

async function loadDashboard(id) {
  try {
    const user = await getUser(id);
    const posts = await getPosts(user.id);
    return { user, posts };
  } catch (err) {
    // any rejection above lands here
    throw new Error('Failed to load dashboard', { cause: err });
  }
}
  • An async function always returns a Promise.
  • await suspends the function (not the thread) until the awaited promise settles; a rejection throws, caught by try/catch.

Converting callback APIs to promises:

  1. util.promisify for error-first callback functions:
const util = require('util');
const readFile = util.promisify(require('fs').readFile);
const data = await readFile('data.txt', 'utf8');
  1. Use built-in promise APIs where available:
const fs = require('fs/promises');
const data = await fs.readFile('data.txt', 'utf8');
  1. Manual wrapping for non-standard callbacks:
function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
await delay(1000);

Common pitfalls:

  • Accidental serialization: awaiting in a loop runs items one-by-one. Use Promise.all with map for parallelism.
  • Missing await: forgetting it means you get a pending Promise instead of the value, and errors go unhandled.
  • forEach + async doesn't await — use for...of or Promise.all(map(...)).