JavaScript Async/Await Table
| Pattern | Code shape | Field note |
|---|---|---|
await fetch(url) | const res = await fetch(url) | The function PAUSES here - the rest runs as a microtask after the promise settles |
sequential awaits | await a(); await b(); | Total time = a + b - only correct when b NEEDS a's result |
parallel with Promise.all | await Promise.all([a(), b()]) | Total time = max(a, b) - START both first, then await the pair |
try/catch around await | try { await f(); } catch (e) | Rejection becomes a throwable - no .catch chains, normal error flow |
allSettled | await Promise.allSettled(ps) | Never rejects - inspect each status; the dashboard/retry pattern |
Promise.any | await Promise.any(ps) | First FULFILLED wins - mirror of race; loses only if all reject (AggregateError) |
top-level await | await at module top level | ES modules only - callers importing you wait for your setup |
floating promise | f(); (no await) | Fire-and-forget swallows rejections - always await or .catch, even when ignoring |
async/await is promise syntax, not a new engine: await pauses the function until the promise settles (the rest resumes as a microtask), and a rejection surfaces as a plain exception you catch with try/catch. The mechanism is the event loop's; the readability is the syntax's.
Bottom line: await does not create parallelism - it makes asynchronous code READ sequentially. Three one-second fetches awaited one after another take three seconds; started together and awaited with Promise.all they take one. The parallelism is yours to structure: start independent work first, await the bundle second.
The honest part: every promise you do not await (or .catch) is a floating promise - if it rejects, nothing handles it and the error is silently dropped, surfacing only as an unhandledrejection console line in production, long after the code that caused it has moved on.
How to use
- Sequence only real dependencies: await getUser() BEFORE await getOrders(user.id) - but fetch dashboard widgets together with Promise.all.
- Handle rejection with normal flow: wrap awaits in try/catch, and put the try around the SMALLEST block that can fail so the fallback is precise.
- Audit for floating promises: any async call without await or .catch is a dropped error report - lint rules like no-floating-promises catch what review misses.
Frequently asked questions
Does await make my code run in parallel?
No - it makes it pause. The parallelism decision happens at the START of the work, not the await: calling two async functions immediately runs both bodies concurrently up to their first await, and THEN awaiting their promises collects results. Sequential awaits (await a(); await b();) force b to start only after a finishes, so latencies add. Promise.all([a(), b()]) runs the cost as the slower of the two. Rule of thumb: dependencies get sequential awaits, independent work gets started together and awaited together.
How is Promise.all different from Promise.allSettled?
Promise.all is fail-fast: the first rejection rejects the whole bundle, and you lose the results of everything that succeeded - right for 'all inputs required' paths like loading a page's critical data. Promise.allSettled never rejects; it resolves with a status list ({status: 'fulfilled', value} or {status: 'rejected', reason}) so you can render successes and retry failures independently - right for dashboards, widgets and any mix where one failure should not blank the page. Choosing between them is really choosing whether a partial result is acceptable.
What is a floating promise and why does it matter?
An async call whose promise is neither awaited nor .catch-ed. The function keeps running, but if the promise rejects, nobody handles the rejection - the error vanishes into an unhandledrejection event, production telemetry sees a symptom far from the cause, and users see a silent failure. It usually comes from treating async functions as fire-and-forget (f(); without await). The discipline: every promise is either awaited, explicitly caught, or deliberately marked as background work with a comment and a top-level .catch that reports.
When do I need Promise.race versus Promise.any?
Promise.race settles with the FIRST settled promise, fulfilled OR rejected - the timeout pattern lives here: race(fetch, timeout(5000)) means a slow request loses to your timer. Promise.any waits for the first FULFILLED promise and ignores rejections along the way - mirror-to-mirror retries live here: ask three mirrors, take whichever answers first; it rejects only when all reject (with an AggregateError listing every failure). Race adopts any outcome; any holds out for a success - picking the wrong one turns your retry logic into an error amplifier.