Async/Await
Learning Objectives
- By the end of this lesson, you will be able to:
- - Understand async functions
- - Use the `await` keyword
- - Handle errors with try-catch in async code
- - Compare async/await with Promises
- - Write clean, readable asynchronous code
- - Use async/await in practical scenarios
Lesson 9.3: Async/Await
Learning Objectives
By the end of this lesson, you will be able to:
- Understand async functions
- Use the
awaitkeyword - Handle errors with try-catch in async code
- Compare async/await with Promises
- Write clean, readable asynchronous code
- Use async/await in practical scenarios
Introduction to Async/Await
Async/await is syntactic sugar built on top of Promises. It makes asynchronous code look and behave more like synchronous code, making it easier to read and write.
Why Async/Await?
- Readable: Looks like synchronous code
- Simple: Easier than Promise chains
- Error Handling: Use try-catch like synchronous code
- Modern: Preferred in modern JavaScript
- Less Nesting: Avoid callback hell and deep Promise chains
async Functions
An async function is a function that returns a Promise. The function body can use the await keyword.
Basic async Function
async function myFunction() {
return "Hello";
}
myFunction().then((result) => {
console.log(result); // "Hello"
});
async Function Always Returns Promise
async function getValue() {
return 42; // Automatically wrapped in Promise
}
getValue().then((value) => {
console.log(value); // 42
});
async Function with Promise
async function fetchData() {
return new Promise((resolve) => {
setTimeout(() => resolve("Data"), 1000);
});
}
fetchData().then((data) => {
console.log(data); // "Data"
});
await Keyword
The await keyword pauses the execution of an async function until a Promise is settled.
Basic await
async function fetchUser() {
let user = await fetchUserData();
console.log(user);
return user;
}
await with Promises
function fetchUserData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: "Alice" });
}, 1000);
});
}
async function getUser() {
let user = await fetchUserData();
console.log("User:", user); // { id: 1, name: "Alice" }
return user;
}
getUser();
await Pauses Execution
async function example() {
console.log("1. Start");
let result = await new Promise((resolve) => {
setTimeout(() => resolve("Done"), 1000);
});
console.log("2. After await:", result);
console.log("3. End");
}
example();
// Output:
// 1. Start
// (wait 1 second)
// 2. After await: Done
// 3. End
Error Handling with try-catch
Async/await allows using try-catch for error handling, just like synchronous code.
Basic Error Handling
async function fetchData() {
try {
let data = await fetchUserData();
console.log("Success:", data);
return data;
} catch (error) {
console.log("Error:", error.message);
throw error;
}
}
try-catch with Promises
function fetchUserData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
let success = Math.random() > 0.5;
if (success) {
resolve({ id: 1, name: "Alice" });
} else {
reject(new Error("Failed to fetch"));
}
}, 1000);
});
}
async function getUser() {
try {
let user = await fetchUserData();
console.log("User:", user);
return user;
} catch (error) {
console.log("Error:", error.message);
return null;
}
}
getUser();
Multiple await with Error Handling
async function processUser() {
try {
let user = await fetchUser();
let posts = await fetchPosts(user.id);
let comments = await fetchComments(posts[0].id);
return { user, posts, comments };
} catch (error) {
console.log("Error in processUser:", error.message);
throw error;
}
}
Async/Await vs Promises
Promise Chain
fetchUser()
.then((user) => {
return fetchPosts(user.id);
})
.then((posts) => {
return fetchComments(posts[0].id);
})
.then((comments) => {
console.log("Comments:", comments);
})
.catch((error) => {
console.log("Error:", error);
});
Async/Await Equivalent
async function getComments() {
try {
let user = await fetchUser();
let posts = await fetchPosts(user.id);
let comments = await fetchComments(posts[0].id);
console.log("Comments:", comments);
} catch (error) {
console.log("Error:", error);
}
}
Comparison
Promises:
- Chainable
- Functional style
- Can be complex with nesting
Async/Await:
- Sequential style
- Easier to read
- Natural error handling
- Looks like synchronous code
Sequential vs Parallel Execution
Sequential with await
async function sequential() {
let user = await fetchUser(1); // Wait for user
let posts = await fetchPosts(1); // Wait for posts
let comments = await fetchComments(1); // Wait for comments
return { user, posts, comments };
}
// Total time: sum of all operations
Parallel with Promise.all()
async function parallel() {
let [user, posts, comments] = await Promise.all([
fetchUser(1),
fetchPosts(1),
fetchComments(1)
]);
return { user, posts, comments };
}
// Total time: longest operation
Mixed Approach
async function mixed() {
// First, get user (required)
let user = await fetchUser(1);
// Then, fetch posts and comments in parallel
let [posts, comments] = await Promise.all([
fetchPosts(user.id),
fetchComments(user.id)
]);
return { user, posts, comments };
}
Practical Examples
Example 1: API Calls
async function fetchUserData(userId) {
try {
let response = await fetch(`/api/users/${userId}`);
let user = await response.json();
return user;
} catch (error) {
console.log("Error fetching user:", error);
throw error;
}
}
async function displayUser(userId) {
try {
let user = await fetchUserData(userId);
console.log("User:", user);
} catch (error) {
console.log("Failed to display user");
}
}
Example 2: Sequential Operations
async function login(username, password) {
try {
// Step 1: Authenticate
let auth = await authenticate(username, password);
console.log("Authenticated:", auth.token);
// Step 2: Fetch user data
let user = await fetchUserData(auth.token);
console.log("User data:", user);
// Step 3: Load user preferences
let preferences = await loadPreferences(user.id);
console.log("Preferences:", preferences);
return { user, preferences };
} catch (error) {
console.log("Login failed:", error.message);
throw error;
}
}
Example 3: Parallel Operations
async function loadDashboard(userId) {
try {
// Load all data in parallel
let [user, posts, notifications, settings] = await Promise.all([
fetchUser(userId),
fetchPosts(userId),
fetchNotifications(userId),
fetchSettings(userId)
]);
return {
user,
posts,
notifications,
settings
};
} catch (error) {
console.log("Failed to load dashboard:", error);
throw error;
}
}
Example 4: Error Handling
async function processOrder(orderId) {
try {
let order = await fetchOrder(orderId);
if (!order) {
throw new Error("Order not found");
}
let payment = await processPayment(order);
let confirmation = await sendConfirmation(order, payment);
return confirmation;
} catch (error) {
// Handle different error types
if (error.message === "Order not found") {
console.log("Order doesn't exist");
} else if (error.message === "Payment failed") {
console.log("Payment processing failed");
} else {
console.log("Unexpected error:", error);
}
throw error;
}
}
Advanced Patterns
Pattern 1: Retry Logic
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
let response = await fetch(url);
return await response.json();
} catch (error) {
if (i === maxRetries - 1) {
throw error;
}
console.log(`Retry ${i + 1}/${maxRetries}`);
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
Pattern 2: Timeout
async function fetchWithTimeout(url, timeout = 5000) {
let fetchPromise = fetch(url);
let timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error("Timeout")), timeout);
});
try {
let response = await Promise.race([fetchPromise, timeoutPromise]);
return await response.json();
} catch (error) {
console.log("Request failed:", error.message);
throw error;
}
}
Pattern 3: Conditional await
async function loadData(loadUser, loadPosts) {
let data = {};
if (loadUser) {
data.user = await fetchUser();
}
if (loadPosts) {
data.posts = await fetchPosts();
}
return data;
}
Practice Exercise
Exercise: Async/Await Practice
Objective: Practice using async/await for asynchronous operations.
Instructions:
-
Create a file called
async-await-practice.js -
Practice:
- Creating async functions
- Using await keyword
- Error handling with try-catch
- Sequential and parallel execution
- Real-world scenarios
Example Solution:
// Async/Await Practice
console.log("=== Basic async Function ===");
async function basicAsync() {
return "Hello from async";
}
basicAsync().then((result) => {
console.log("Result:", result);
});
console.log();
console.log("=== await with Promises ===");
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ id: 1, name: "Alice" });
}, 1000);
});
}
async function getData() {
console.log("Fetching data...");
let data = await fetchData();
console.log("Data received:", data);
return data;
}
getData();
console.log();
console.log("=== Sequential Execution ===");
function step1() {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Step 1 complete");
resolve("Step 1 result");
}, 1000);
});
}
function step2(data) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Step 2 complete with:", data);
resolve("Step 2 result");
}, 1000);
});
}
function step3(data) {
return new Promise((resolve) => {
setTimeout(() => {
console.log("Step 3 complete with:", data);
resolve("Final result");
}, 1000);
});
}
async function sequential() {
try {
let result1 = await step1();
let result2 = await step2(result1);
let result3 = await step3(result2);
console.log("All steps complete:", result3);
} catch (error) {
console.log("Error:", error);
}
}
sequential();
console.log();
console.log("=== Parallel Execution ===");
function fetchUser(userId) {
return new Promise((resolve) => {
let delay = Math.random() * 1000;
setTimeout(() => {
resolve({ id: userId, name: `User ${userId}` });
}, delay);
});
}
function fetchPosts(userId) {
return new Promise((resolve) => {
let delay = Math.random() * 1000;
setTimeout(() => {
resolve([
{ id: 1, title: "Post 1" },
{ id: 2, title: "Post 2" }
]);
}, delay);
});
}
async function parallel() {
try {
let [user, posts] = await Promise.all([
fetchUser(1),
fetchPosts(1)
]);
console.log("User:", user);
console.log("Posts:", posts);
} catch (error) {
console.log("Error:", error);
}
}
setTimeout(() => {
parallel();
}, 4000);
console.log();
console.log("=== Error Handling ===");
function mightFail(shouldFail) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (shouldFail) {
reject(new Error("Operation failed"));
} else {
resolve("Operation succeeded");
}
}, 1000);
});
}
async function handleErrors() {
try {
let result = await mightFail(false);
console.log("Success:", result);
let result2 = await mightFail(true);
console.log("This won't run");
} catch (error) {
console.log("Caught error:", error.message);
}
}
setTimeout(() => {
handleErrors();
}, 7000);
console.log();
console.log("=== Real-World Example: API Calls ===");
function apiCall(endpoint) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (endpoint === "/users") {
resolve([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]);
} else if (endpoint === "/posts") {
resolve([
{ id: 1, title: "Post 1" },
{ id: 2, title: "Post 2" }
]);
} else {
reject(new Error("Endpoint not found"));
}
}, 1000);
});
}
async function loadData() {
try {
// Sequential
console.log("Loading users...");
let users = await apiCall("/users");
console.log("Users loaded:", users);
console.log("Loading posts...");
let posts = await apiCall("/posts");
console.log("Posts loaded:", posts);
return { users, posts };
} catch (error) {
console.log("Error loading data:", error.message);
throw error;
}
}
setTimeout(() => {
loadData();
}, 10000);
console.log();
console.log("=== Mixed Sequential and Parallel ===");
async function mixed() {
try {
// First, get user (required)
console.log("Fetching user...");
let user = await fetchUser(1);
console.log("User:", user);
// Then, fetch related data in parallel
console.log("Fetching related data in parallel...");
let [posts, comments] = await Promise.all([
fetchPosts(user.id),
fetchComments(user.id)
]);
console.log("Posts:", posts);
console.log("Comments:", comments);
return { user, posts, comments };
} catch (error) {
console.log("Error:", error);
}
}
setTimeout(() => {
mixed();
}, 13000);
Expected Output:
=== Basic async Function ===
Result: Hello from async
=== await with Promises ===
Fetching data...
Data received: { id: 1, name: "Alice" }
=== Sequential Execution ===
Step 1 complete
Step 2 complete with: Step 1 result
Step 3 complete with: Step 2 result
All steps complete: Final result
=== Parallel Execution ===
Posts: [ { id: 1, title: "Post 1" }, { id: 2, title: "Post 2" } ]
User: { id: 1, name: "User 1" }
=== Error Handling ===
Success: Operation succeeded
Caught error: Operation failed
=== Real-World Example: API Calls ===
Loading users...
Users loaded: [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" } ]
Loading posts...
Posts loaded: [ { id: 1, title: "Post 1" }, { id: 2, title: "Post 2" } ]
=== Mixed Sequential and Parallel ===
Fetching user...
User: { id: 1, name: "User 1" }
Fetching related data in parallel...
Posts: [ { id: 1, title: "Post 1" }, { id: 2, title: "Post 2" } ]
Comments: [ { id: 1, text: "Comment 1" } ]
Challenge (Optional):
- Build async/await API wrapper
- Create retry logic with async/await
- Build timeout utilities
- Create async utility functions
Common Mistakes
1. Forgetting await
// ⚠️ Problem: Returns Promise, not value
async function getData() {
let data = fetchData(); // Missing await
return data; // Returns Promise, not data
}
// ✅ Solution: Use await
async function getData() {
let data = await fetchData();
return data;
}
2. await in Non-async Function
// ❌ Error: await only in async functions
function getData() {
let data = await fetchData(); // Error!
}
// ✅ Solution: Make function async
async function getData() {
let data = await fetchData();
}
3. Not Handling Errors
// ⚠️ Problem: Unhandled promise rejection
async function getData() {
let data = await fetchData(); // What if it fails?
return data;
}
// ✅ Solution: Use try-catch
async function getData() {
try {
let data = await fetchData();
return data;
} catch (error) {
console.log("Error:", error);
throw error;
}
}
4. Sequential When Parallel Possible
// ⚠️ Problem: Unnecessarily sequential
async function loadData() {
let user = await fetchUser();
let posts = await fetchPosts(); // Could be parallel
return { user, posts };
}
// ✅ Solution: Use Promise.all() for parallel
async function loadData() {
let [user, posts] = await Promise.all([
fetchUser(),
fetchPosts()
]);
return { user, posts };
}
Key Takeaways
- async Function: Always returns a Promise
- await: Pauses execution until Promise settles
- Error Handling: Use try-catch like synchronous code
- Sequential: Use await for dependent operations
- Parallel: Use Promise.all() for independent operations
- Readability: Much cleaner than Promise chains
- Best Practice: Always handle errors, use parallel when possible
- Modern Standard: Preferred over callbacks and Promise chains
Quiz: Async/Await
Test your understanding with these questions:
-
async function always returns:
- A) Value
- B) Promise
- C) undefined
- D) Error
-
await can only be used:
- A) In any function
- B) In async functions
- C) In Promises
- D) Never
-
await pauses:
- A) Entire program
- B) Async function execution
- C) Nothing
- D) Browser
-
Error handling in async/await uses:
- A) catch()
- B) try-catch
- C) then()
- D) finally()
-
For parallel operations, use:
- A) Multiple await
- B) Promise.all()
- C) Sequential await
- D) Callbacks
-
async/await is:
- A) Different from Promises
- B) Syntactic sugar over Promises
- C) Replaces Promises
- D) Not related to Promises
-
Missing await returns:
- A) Value
- B) Promise
- C) undefined
- D) Error
Answers:
- B) Promise
- B) In async functions
- B) Async function execution
- B) try-catch
- B) Promise.all()
- B) Syntactic sugar over Promises
- B) Promise
Next Steps
Congratulations! You've completed Module 9: Asynchronous JavaScript. You now know:
- How callbacks work
- How to use Promises
- How to use async/await
- Modern asynchronous patterns
What's Next?
- Module 10: Iterators and Generators
- Practice combining async patterns
- Build real-world async applications
- Continue learning advanced JavaScript
Additional Resources
- MDN: async function: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
- MDN: await: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await
- JavaScript.info: Async/await: javascript.info/async-await
- Async/Await Best Practices: Common patterns and pitfalls
Lesson completed! You've finished Module 9: Asynchronous JavaScript. Ready for Module 10: Iterators and Generators!