You're debugging a production issue at 2 AM. The stack trace points to a Promise chain three levels deep. Somewhere, an unhandled rejection slipped through. The code works locally. It passes CI. But in production, it fails silently â leaving users staring at a frozen UI. This isn't a hypothetical. It's the reality of async JavaScript when you treat async/await as syntactic sugar instead of a control flow primitive.
Why Async/Await Changes Everything
async/await didn't just make promises readable. It fundamentally shifted how we reason about time in code. Synchronous-looking code that yields control. Try/catch that actually catches async errors. Variables that hold values, not promise objects. But the mental model shift is where most developers stall.
The Event Loop Reality Check
Every await yields to the event loop. Microtasks queue. Macrotasks wait. This matters when you're processing 10,000 items or handling WebSocket bursts. Sequential await in a loop kills throughput. Parallel execution with Promise.all restores it â but introduces new failure modes.
Error Handling That Actually Works
The biggest lie in async JS: try/catch catches everything. It doesn't. Unhandled rejections from detached promises. Errors in Promise.all that reject immediately but leave siblings running. Top-level await in modules that crashes the process. You need layers.
Cancellation: The Missing Primitive
JavaScript still lacks native cancellation. AbortController is the standard pattern, but it's opt-in. Every async function must accept a signal. Every library must respect it. When they don't, you leak requests, timers, and memory.
Common Pitfalls in 2026
| Pitfall | Symptom | Fix |
|---|---|---|
| forEach with await | Runs sequentially, ignores promises | Use for...of or Promise.all |
| Missing await | Returns promise, not value | ESLint: require-await |
| Swallowed errors | Silent failures in background tasks | Always .catch or try/catch |
| Zombie promises | Memory leaks from abandoned chains | AbortController + cleanup |
| Top-level await in CJS | SyntaxError | Use ESM or async IIFE |
"Async/await makes asynchronous code look synchronous. The danger is forgetting it isn't.
â Jake Archibald
Your Next Steps
Audit your codebase this week. Search for .then() chains longer than two. Find forEach with async callbacks. Check every Promise.all for error boundaries. Add AbortController to your longest-running requests. Enable the no-floating-promises ESLint rule. Then measure: reduced unhandled rejections, faster page loads, fewer 2 AM pages.










