async & await
Write asynchronous code that reads top-to-bottom, handle errors, and run tasks in parallel.
async and await are syntax built on top of promises that lets asynchronous code read like ordinary top-to-bottom code. There's nothing new underneath — just promises — but the result is far easier to write, read and debug. Most modern JavaScript uses async/await for anything asynchronous.
From .then to await#
Here's a promise chain:
And the same logic with async/await:
asyncbefore a function makes it always return a promise.awaitpauses that function until the promise settles, then gives you the fulfilled value (or throws the rejection reason).- While it's paused, the rest of the program keeps running —
awaitdoesn't block the thread.
async functions return promises#
Returning a value fulfils the promise; throwing rejects it. Every form of function can be async:
Error handling with try/catch#
The big win: ordinary try/catch/finally works with asynchronous code:
These examples use top-level
await, which works in ES modules: save as.mjs(or use"type": "module") to run with Node, or use a<script type="module">in the browser. In a regular script, wrap the code in anasyncfunction.
Sequential vs parallel#
await waits for each promise in turn. That's right when one step needs the previous step's result:
But for independent work, awaiting one after another wastes time:
Start everything that doesn't depend on something else, then await them together. Use Promise.allSettled when some may fail and you still want the rest.
Loops and await#
Sequential: for...of
Parallel: map + Promise.all
The forEach trap
forEach calls the async callback and ignores the promise it returns. Use for...of or Promise.all instead.
Limiting concurrency
Firing 1,000 requests at once can overwhelm a server (or get you rate-limited). Process in batches:
Retrying with back-off#
A common real-world helper — retry a flaky operation, waiting longer each time:
Note return await fn() inside try: the await is needed so a rejection is caught by this catch.
Cancelling with AbortController#
Promises can't be cancelled by themselves, but many APIs (including fetch) accept an AbortSignal:
You'll use this pattern for search-as-you-type and React effects.
Don't forget to await#
Forgetting await is the most common async bug. Linters (ESLint's no-floating-promises with TypeScript) can catch it for you.
Common mistakes#
- Forgetting
await, so you get aPromise { <pending> }instead of a value. - Awaiting independent tasks one by one instead of using
Promise.all. - Using
awaitinsideforEach,maporfiltercallbacks and expecting the loop to wait. - Using
awaitin a non-async function:SyntaxError: await is only valid in async functions and the top level bodies of modules. - Swallowing errors with an empty
catch.
What's next#
Now put async/await to work talking to real servers with fetch, APIs and JSON.
Check your understanding
Quick quiz
1.What does an
asyncfunction always return?2.Two independent requests are written as
const a = await getA(); const b = await getB();. How can you make them run at the same time?3.Why doesn't
items.forEach(async (item) => { await save(item); })wait for all saves to finish?
Finished reading?
Mark this lesson complete to track your progress.