TK
All writing
Frontend

async/await in JavaScript, finally explained

Promises, async, await and the pitfalls that trip everyone up — sequential vs parallel awaits, error handling with try/catch, and the forgotten-await bug that causes silent failures.

Tanveer Khan10 min read

async/await made asynchronous JavaScript readable, but it also hides what's really happening, which is how subtle bugs sneak in. Here's the mental model plus the three mistakes I see most often.

await just unwraps a Promise

async.ts
ts
// a function marked async always returns a Promise
async function getUser(id: string) {
  const res = await fetch(`/api/users/${id}`); // pause until it resolves
  return res.json();                            // also awaited implicitly
}

// callers await it too
const user = await getUser("42");

Mistake 1: sequential when you meant parallel

Awaiting in a loop runs requests ONE AT A TIME. If they don't depend on each other, that's needless slowness — three 1s requests take 3s instead of 1s.

parallel.ts
ts
// ❌ sequential — 3 seconds
for (const id of ids) {
  users.push(await getUser(id));
}

// ✅ parallel — ~1 second
const users = await Promise.all(ids.map((id) => getUser(id)));

Mistake 2: forgetting await

Forget await and you get a Promise object instead of the value — and any error inside becomes an unhandled rejection that fails silently.

forgot-await.ts
ts
// ❌ user is a Promise, not a user; the save may not have finished
const user = getUser("42");
console.log(user.name);        // undefined

// ✅
const user = await getUser("42");

Mistake 3: error handling

errors.ts
ts
// wrap awaits that can throw in try/catch
try {
  const user = await getUser("42");
  return user;
} catch (err) {
  // network error, non-2xx if you throw on it, JSON parse failure...
  logger.error("failed to load user", err);
  throw err; // or return a fallback
}

Promise.all rejects as soon as ANY promise rejects. If you want every result regardless of individual failures, use Promise.allSettled and inspect each outcome.

Remember three things: await unwraps a promise and pauses the function; parallelise independent awaits with Promise.all; and every await that can throw needs a try/catch. That covers the vast majority of real async code.