JavaScript Async/Await Cheat Sheet
Quick-reference for async/await patterns. Each section includes copy-ready snippets with inline output comments.
Async Function Basics
An async function always returns a Promise. await pauses execution until the Promise settles, then unwraps the value.
async function fetchData(url) {
const res = await fetch(url);
return res.json(); // auto-wrapped in Promise
}const fetchData = async (url) => {
const res = await fetch(url);
return res.json();
};async function greet() {
return 'hello';
}
greet() // => Promise { 'hello' }
await greet() // => 'hello'Even returning a plain value wraps it in a resolved Promise. Forgetting await gives you the Promise object, not the value.
// In an ES module (.mjs or type: "module")
const data = await fetch('/api/config').then(r => r.json());
console.log(data);Error Handling with try/catch
Wrap await in try/catch to handle rejections. Without it, unhandled rejections crash Node.js (v15+) or log silently in browsers.
try {
const data = await fetchData('/api/users');
process(data);
} catch (err) {
console.error('Request failed:', err.message);
}// BUG: rejection bypasses catch
async function bad() {
try {
return fetch('/api'); // returns pending Promise
} catch (err) {
return fallback; // never runs!
}
}
// CORRECT: await so catch can handle rejection
async function good() {
try {
return await fetch('/api');
} catch (err) {
return fallback; // runs on rejection
}
}Inside try/catch, always use return await. Outside try/catch, return await is redundant.
async function oops() {
throw new Error('Something broke');
}
oops(); // No await, no .catch()
// UnhandledPromiseRejection warning!
// Fix: always handle rejections
oops().catch(console.error);Parallel: Promise.all
Run independent async operations in parallel. Fails fast — if any Promise rejects, the whole result rejects.
const [user, posts] = await Promise.all([
fetchUser(id),
fetchPosts(id),
]);const urls = ['/api/a', '/api/b', '/api/c'];
const results = await Promise.all(
urls.map(url => fetch(url).then(r => r.json()))
);try {
const [a, b] = await Promise.all([
fetch('/api/a'), // succeeds
fetch('/api/b'), // rejects
]);
} catch (err) {
// Entire Promise.all rejects on first failure
console.error(err);
}If any one Promise rejects, you lose all results. Use Promise.allSettled when partial success is OK.
Partial Success: Promise.allSettled
Waits for all Promises to settle (fulfill or reject). Returns an array of result objects with status, value, or reason.
const results = await Promise.allSettled([
fetch('/api/users'),
fetch('/api/posts'), // might fail
fetch('/api/settings'),
]);
for (const result of results) {
if (result.status === 'fulfilled') {
process(result.value);
} else {
console.error('Failed:', result.reason);
}
}const results = await Promise.allSettled(promises);
const successes = results
.filter(r => r.status === 'fulfilled')
.map(r => r.value);Race and Any
Promise.race settles with whichever Promise finishes first. Promise.any waits for the first fulfillment, ignoring rejections.
function timeout(ms) {
return new Promise((_, reject) =>
setTimeout(() => reject(new Error('Timeout')), ms)
);
}
const data = await Promise.race([
fetch('/api/data').then(r => r.json()),
timeout(5000),
]);try {
const fastest = await Promise.any([
fetch('https://cdn1.example.com/data'),
fetch('https://cdn2.example.com/data'),
]);
const data = await fastest.json();
} catch (err) {
// AggregateError: all promises rejected
console.log(err.errors);
}// Promise.all — all must fulfill (fail-fast)
// Promise.allSettled — wait for all, report each
// Promise.race — first to settle (fulfill or reject)
// Promise.any — first to fulfill (ignore rejections)Sequential vs Parallel Execution
Sequential awaits in loops run one at a time. For independent work, use Promise.all for parallelism.
// Each iteration waits for the previous one
for (const url of urls) {
const data = await fetch(url);
await processData(data);
}// All requests fire simultaneously
const results = await Promise.all(
urls.map(async url => {
const res = await fetch(url);
return res.json();
})
);// BUG: forEach ignores async callbacks
urls.forEach(async (url) => {
await fetch(url); // fires but is not awaited
});
console.log('Done!'); // runs immediately!
// Fix: use for...of or Promise.all
for (const url of urls) {
await fetch(url);
}forEach returns undefined, not a Promise. It cannot await async callbacks. Use for...of or map + Promise.all.
Cancellation: AbortController
Cancel in-flight fetch requests using AbortController. Essential for component unmounts, superseded requests, and timeouts.
const controller = new AbortController();
const promise = fetch('/api/data', {
signal: controller.signal,
});
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const res = await promise;
const data = await res.json();
} catch (err) {
if (err.name === 'AbortError') {
console.log('Request cancelled');
} else {
throw err;
}
}useEffect(() => {
const controller = new AbortController();
async function loadData() {
const res = await fetch(url, { signal: controller.signal });
setData(await res.json());
}
loadData().catch(() => {});
return () => controller.abort(); // cancel on unmount
}, [url]);Async Iteration: for await...of
Iterate over async iterables like ReadableStreams, async generators, or any object with [Symbol.asyncIterator].
const response = await fetch('/api/stream');
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
console.log(decoder.decode(value));
}async function* paginate(url) {
let page = 1;
while (true) {
const res = await fetch(`${url}?page=${page}`);
const data = await res.json();
if (data.length === 0) return;
yield data;
page++;
}
}
for await (const page of paginate('/api/items')) {
process(page);
}for await (const chunk of readableStream) {
process(chunk);
}for await...of only works inside async functions. The iterable must implement [Symbol.asyncIterator].
Common Async Patterns
Practical patterns for real-world async JavaScript.
(async () => {
const data = await fetch('/api/config').then(r => r.json());
console.log(data);
})();Less needed with top-level await in ES modules, but still common in scripts and legacy code.
async function retry(fn, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
if (i === attempts - 1) throw err;
await new Promise(r => setTimeout(r, 2 ** i * 1000));
}
}
}
const data = await retry(() => fetch('/api/flaky'));let controller;
async function search(query) {
controller?.abort();
controller = new AbortController();
const res = await fetch(
`/api/search?q=${query}`,
{ signal: controller.signal }
);
return res.json();
}Can you write this from memory?
Create a Promise that resolves to "Success".