Async/await
What is async / await?
async / await is syntax for working with promises that lets async code read more like top-to-bottom code.
function wait(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
async function example() {
console.log("Start");
await wait(10);
console.log("End");
}
example();
Why async / await?
async / await is popular because it:
- Reads like normal control flow (
try/catch,if,for) - Avoids deeply nested callbacks
- Avoids long
.then(...)chains in many cases
async functions
An async function always returns a promise:
async function fetchData() {
return "Data";
}
// Equivalent to:
function fetchData() {
return Promise.resolve("Data");
}
Even if you return a regular value, it gets wrapped in a promise automatically.
Calling async functions
Since async functions return promises, you can use them with .then():
async function getData() {
return "Hello";
}
getData()
.then(data => {
console.log(data); // "Hello"
});
But you'll usually use await instead (see below).
The await keyword
Use await to wait for a promise to settle. It pauses the current async function until the promise resolves (or rejects), then gives you the resolved value (or throws the rejection error).
async function getUser() {
// Example async API:
const user = await fetchUser(userId);
console.log(user);
}
Without await, you'd get a promise object. With await, you get the actual value.
await only works in async functions
You can only use await inside an async function:
// Good
async function example() {
const data = await fetchData();
}
// Error - can't use await here
function example() {
const data = await fetchData(); // SyntaxError!
}
Some environments also support top-level await in modules. As a beginner default, use await inside an async function.
Chaining with async/await
Compare promises vs async/await for chaining operations:
With promises:
// Example async APIs:
fetchUser(userId)
.then(user => fetchPosts(user.id))
.then(posts => fetchComments(posts[0].id))
.then(comments => console.log(comments))
.catch(error => console.error(error));
With async/await:
async function getComments() {
// Example async APIs:
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
console.log(comments);
}
The functions in these examples (fetchUser, fetchPosts, fetchComments) are placeholders to show how chaining works.
Error handling
Use try/catch with async / await:
async function fetchData() {
try {
// Example async API:
const data = await fetchFromServer();
console.log(data);
} catch (error) {
console.error("Error:", error);
}
}
This works because await turns a rejected promise into a thrown error.
Handling errors in promise chains
If you mix promises and async/await, you can still use .catch():
async function example() {
try {
const data = await fetchData()
.catch(error => {
console.error("Fetch failed:", error);
return null; // Return default value
});
if (data) {
console.log(data);
}
} catch (error) {
console.error("Other error:", error);
}
}
But try/catch is usually simpler.
Common patterns
Sequential operations
Run operations one after another (each waits for the previous):
async function sequential() {
// Example async APIs:
const user = await fetchUser(id);
const posts = await fetchPosts(user.id);
const comments = await fetchComments(posts[0].id);
return comments;
}
Parallel operations
Run multiple operations simultaneously using Promise.all():
async function parallel() {
const [user, posts, settings] = await Promise.all([
// Example async APIs:
fetchUser(id),
fetchPosts(id),
fetchSettings(id)
]);
return { user, posts, settings };
}
All three operations start immediately, then you wait for all of them to complete.
Rule of thumb: use await for sequential steps, and Promise.all(...) for independent work you can do in parallel.
Looping with async
Process items one at a time:
async function processItems(items) {
for (const item of items) {
await processItem(item); // Wait for each item
}
}
Or process all items in parallel:
async function processItemsParallel(items) {
await Promise.all(items.map(item => processItem(item)));
}
Be careful with parallel loops if the list can be very large. Starting thousands of async operations at once can overload the system.
Converting callback code to async/await
Many older APIs use callbacks, but you can wrap them in promises, then use async/await:
// Old callback API
function delay(callback, ms) {
setTimeout(callback, ms);
}
// Wrap in promise
function delayPromise(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
// Now use with async/await
async function doSomething() {
await delayPromise(1000); // Wait 1 second
console.log("Done waiting!");
}
Real-world example
Here's a practical example combining everything:
async function getUserProfile(userId) {
try {
// Fetch data in parallel
const [user, posts] = await Promise.all([
fetch(`/api/users/${userId}`).then(r => r.json()),
fetch(`/api/users/${userId}/posts`).then(r => r.json()),
]);
// Process posts
const postCount = posts.length;
const recentPosts = posts.slice(0, 5);
return {
user,
postCount,
recentPosts
};
} catch (error) {
console.error("Failed to fetch profile:", error);
throw error; // Re-throw to let caller handle
}
}
// Usage
async function run() {
try {
const profile = await getUserProfile(123);
console.log(profile);
} catch (error) {
console.error("Error loading profile:", error);
}
}
run();
Common mistakes
Forgetting await
// Wrong - returns a promise, not the value
async function example() {
const data = fetchData(); // Missing await!
console.log(data); // Promise object, not the data
}
// Correct
async function example() {
const data = await fetchData();
console.log(data); // Actual data
}
Forgetting async
// Wrong - can't use await without async
function example() {
const data = await fetchData(); // SyntaxError!
}
// Correct
async function example() {
const data = await fetchData();
}
Awaiting independent work sequentially
If two async operations do not depend on each other, awaiting them one-by-one is slower than waiting in parallel:
// Slower: runs one after the other
const user = await fetchUser(id);
const settings = await fetchSettings(id);
// Faster: start both, then await both
const [user, settings] = await Promise.all([fetchUser(id), fetchSettings(id)]);
Not handling errors
// Bad - unhandled rejection
async function badExample() {
await failingPromise(); // Error not caught!
}
// Good - handle errors
async function goodExample() {
try {
await failingPromise();
} catch (error) {
console.error("Handled:", error);
}
}
Summary
asyncfunctions always return promisesawaitpauses execution until a promise resolves- Use
try/catchfor error handling (same as synchronous code) - Chain operations naturally—no
.then()needed - Use
Promise.all()for parallel operations async/awaitis the most common way to write promise-based code