On this page
Async/Await
What is async/await?
async/await is syntactic sugar over promises that makes asynchronous code read as if it were synchronous. Introduced in ES2017, it is the preferred way to work with asynchronous operations in modern JavaScript.
The async keyword
Marking a function as async has two effects:
- The function always returns a promise
- It allows using
awaitinside it
async function example() {
return 42; // equivalent to return Promise.resolve(42)
}The await keyword
await pauses the execution of the async function until the promise resolves, and returns its value:
async function getData() {
const result = await somePromise(); // pauses here
console.log(result); // runs when the promise resolves
}Error handling
Instead of .catch(), we use try/catch blocks that are familiar from synchronous programming:
async function load() {
try {
const data = await riskyOperation();
return data;
} catch (error) {
console.error('Failed:', error.message);
return defaultValue;
} finally {
cleanupResources();
}
}The catch block catches both rejected promise errors and exceptions thrown with throw.
Sequential vs parallel
This is one of the most common mistakes with async/await:
Sequential (slow)
const a = await request1(); // waits 1s
const b = await request2(); // waits 1s more
// Total: 2 secondsParallel (fast)
const [a, b] = await Promise.all([request1(), request2()]);
// Total: 1 second (they run at the same time)Use the sequential version only when one request depends on the result of the previous one.
Useful patterns
Processing a list of items
Depending on whether the items are independent or not:
- Sequential — Use
for...ofwithawaitinside the loop - Parallel — Use
.map()with async functions andPromise.all
Retry with exponential backoff
A robust pattern for retrying operations that may temporarily fail (e.g., network requests). Each retry waits exponentially longer (1s, 2s, 4s...).
Top-level await
In ES modules, you can use await directly at the module scope:
// config.js (ES module)
const response = await fetch('/api/config');
export const config = await response.json();async/await vs .then()
| Aspect | async/await | .then() |
|---|---|---|
| Readability | More readable, linear flow | Can become nested |
| Errors | Familiar try/catch | .catch() in chain |
| Debugging | Clear stack traces | Fragmented stack traces |
| Parallelism | Requires Promise.all | Natural with multiple .then |
In practice, async/await is preferred for most cases. Use .then() when you need quick promise composition or in short callbacks.
Practice
- Convert .then() to async/await: Take an existing promise chain with
.then().catch()and rewrite it usingasync/awaitwithtry/catch/finally. Verify that the behavior is identical. - Run tasks in parallel: Write an async function that uses
Promise.allto execute 3 simulated calls (usingsetTimeoutwrapped in promises) in parallel, and measure the total time withperformance.now(). - Implement a basic retry: Create a function
withRetries(fn, attempts)that retries executingfnup toattemptstimes if it fails. Test it with a function that fails randomly.
In the next lesson we will learn how to use the Fetch API to make HTTP requests.
// Async function - always returns a promise
async function getUser(id) {
// await pauses execution until the promise resolves
const response = await fetch(`/api/users/${id}`);
const user = await response.json();
return user;
}
// Error handling with try/catch
async function loadData() {
try {
const user = await getUser(1);
const posts = await fetch(`/api/users/${user.id}/posts`);
const data = await posts.json();
console.log('Posts:', data);
} catch (error) {
console.error('Error loading:', error.message);
} finally {
console.log('Loading finished');
}
}
// Parallel requests with await
async function loadDashboard() {
// BAD: sequential (slow)
// const users = await fetch('/api/users').then(r => r.json());
// const posts = await fetch('/api/posts').then(r => r.json());
// GOOD: parallel (fast)
const [users, posts] = await Promise.all([
fetch('/api/users').then(r => r.json()),
fetch('/api/posts').then(r => r.json()),
]);
return { users, posts };
}
// Async arrow function
const getProfile = async (id) => {
const res = await fetch(`/api/profiles/${id}`);
if (!res.ok) {
throw new Error(`HTTP ${res.status}`);
}
return res.json();
};
// Pattern: process items sequentially
async function processSequential(urls) {
const results = [];
for (const url of urls) {
const res = await fetch(url);
const data = await res.json();
results.push(data);
}
return results;
}
// Pattern: process items in parallel
async function processParallel(urls) {
const promises = urls.map(async (url) => {
const res = await fetch(url);
return res.json();
});
return Promise.all(promises);
}
// Pattern: retry with exponential backoff
async function withRetries(fn, attempts = 3) {
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (error) {
if (i === attempts - 1) throw error;
const wait = Math.pow(2, i) * 1000;
console.log(`Retrying in ${wait}ms...`);
await new Promise(r => setTimeout(r, wait));
}
}
}
// Using the retry
const data = await withRetries(
() => fetch('/api/data').then(r => r.json()),
3
);
Sign in to track your progress