Async JavaScript
Asynchronous programming is core to JavaScript — network requests, timers, file I/O, and user events all complete later, and the language is built to handle them without blocking. Mastering async is the difference between smooth apps and frozen UIs or unhandled-rejection crashes.
The model evolved from callbacks → Promises → async/await. Modern code uses async/await for readability, backed by Promises and their combinators for concurrency. See JavaScript for the underlying event loop.
TL;DR
- Use async/await for readable asynchronous code.
- Always handle rejections (
try/catchor.catch). - Use
Promise.allto run independent operations concurrently. - Don't
awaitsequentially what could run in parallel.
Quick Example
async/await with proper error handling reads like synchronous code:
Core Concepts
Promises
A Promise represents the eventual result of an async operation — pending, fulfilled, or rejected:
Promise combinators
Patterns
Sequential vs parallel
Async iteration
Retry with exponential backoff
Best Practices
- Handle every rejection — an unhandled rejection can crash Node or silently fail in the browser.
- Parallelize independent work with
Promise.all; onlyawaitin sequence when there's a real dependency. - Don't mix callbacks and Promises — promisify callback APIs (
util.promisify). - Add timeouts to network calls (
Promise.racewith a timeout) so they can't hang forever.
Common Mistakes
Unhandled rejection
Awaiting in a loop unnecessarily
FAQ
What's the difference between Promise.all and Promise.allSettled?
Promise.all rejects as soon as any input rejects, giving you all results only if all succeed. Promise.allSettled waits for every promise and returns an array of {status, value/reason} so partial failures don't lose the successes. Use allSettled when you want every outcome regardless of failures.
Should I use callbacks, Promises, or async/await?
async/await for new code — it reads sequentially and handles errors with try/catch. Promises underlie it and are needed for combinators (Promise.all). Callbacks are legacy; promisify callback-based APIs rather than nesting them.
How do I run async operations in parallel?
Start them without awaiting, then await together: Promise.all([a(), b()]) or ids.map(fetchUser) passed to Promise.all. Awaiting each in sequence (await a(); await b()) serializes them unnecessarily when they're independent.
How do I add a timeout to an async call?
Race the operation against a timeout promise: Promise.race([fetch(url), new Promise((_, rej) => setTimeout(() => rej(new Error('timeout')), 5000))]). Modern fetch also supports AbortSignal.timeout(ms).
Related Topics
- JavaScript — The event loop underneath
- Node.js — Server-side async patterns
- Concurrency Patterns — Across languages
- Error Handling — Failure patterns
- Data Fetching — Async in the UI