The 3 Async/Await Mistakes That Still Haunt Production Code in 2026
I spent 2 hours debugging a "weird" bug last week. The function was returning undefined, the API was timing out, and the error messages were completely misleading.
Root cause? A single missing await.
Async/await has been in JavaScript for years now. It's supposed to make async code look synchronous, clean, and readable. But in practice, I see the same mistakes over and over — even from experienced developers.
Let me share the 3 async/await patterns that still break production in 2026.
Mistake #1: The Forgotten Await
This is the classic. The one that wastes hours.
// Bug: fetchData returns a Promise, not data
async function getUser() {
const data = fetchData('/api/user');
return data.name; // TypeError: data.name is undefined
}
// Fix
async function getUser() {
const data = await fetchData('/api/user');
return data.name;
}
Why this happens: The function works fine in simple cases because fetchData looks like it returns data. It only breaks downstream when you try to access properties. TypeScript catches this, but in JS projects? Good luck.
Pro tip: If your linter isn't flagging unhandled promises, you're flying blind. Add @typescript-eslint/no-floating-promises to your config today.
Mistake #2: Sequential Awaits When You Meant Parallel
// Runs in series: ~3 seconds total
async function loadDashboard() {
const users = await fetchUsers(); // ~1s
const posts = await fetchPosts(); // ~1s
const stats = await fetchStats(); // ~1s
return { users, posts, stats };
}
// Runs in parallel: ~1 second total
async function loadDashboard() {
const [users, posts, stats] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchStats()
]);
return { users, posts, stats };
}
Why this happens: When you write await three times in a row, it feels natural. Each line waits for the previous one. But these are independent API calls — they don't need to wait for each other.
Gotcha: Promise.all fails fast — if one promise rejects, you lose all results. If you need partial success, use Promise.allSettled().
Mistake #3: Mixing Async/Await with .then()/.catch()
// This is a mess
async function processOrder(orderId) {
const order = await getOrder(orderId)
.then(o => {
if (!o.items.length) throw new Error('Empty');
return o;
})
.catch(err => {
console.log(err);
return null;
});
if (!order) return;
await shipOrder(order);
}
Why this happens: Developers mix patterns because they're migrating from promise chains, or they think .catch() is "cleaner." The result? Error handling becomes unpredictable.
The fix: Pick one style and stick with it. Async/await with try/catch:
// Clean and predictable
async function processOrder(orderId) {
try {
const order = await getOrder(orderId);
if (!order.items.length) throw new Error('Empty order');
await shipOrder(order);
} catch (err) {
console.error('Order processing failed:', err.message);
}
}
Bonus: The Error Swallow
// This is how bugs hide
async function updateUser(id, data) {
try {
await db.users.update(id, data);
} catch (err) {
// TODO: handle this later
}
}
An empty catch block is a time bomb. If you truly want to ignore an error, document why:
// Intentional: analytics failure shouldn't block checkout
try {
await trackEvent('purchase_complete', { orderId });
} catch (err) {
console.warn('Analytics tracking failed (non-critical):', err.message);
}
The Bottom Line
Async/await made JavaScript async code infinitely more readable. But readability doesn't automatically mean correctness.
Before your next PR review, ask:
- Is every promise handled (awaited or caught)?
- Are independent operations running in parallel?
- Is error handling consistent throughout the function?
These three questions would have saved me those 2 hours last week.
What async bug has wasted the most of your time? Share it in the responses.
Top comments (0)