Quick Answer
Use async function declaration and await keyword for promises. Wrap in try-catch for error handling. Async functions always return promises.
Understanding the Issue
Async/await provides syntactic sugar over promises, making asynchronous code look and behave more like synchronous code. The async keyword declares a function as asynchronous, automatically wrapping the return value in a promise. The await keyword pauses execution until a promise resolves, returning the resolved value. This approach eliminates callback hell and promise chaining complexity while maintaining all the benefits of asynchronous programming.
The Problem
This code demonstrates the issue:
Javascript
Error
// Problem: Complex promise chains
function fetchAndProcessData() {
return fetchUser(123)
.then(user => {
return fetchUserPosts(user.id);
})
.then(posts => {
return posts.map(post => processPost(post));
})
.then(processedPosts => {
return Promise.all(processedPosts);
})
.catch(error => {
console.error("Error in chain:", error);
throw error;
});
}
// How to make this more readable?
fetchAndProcessData();
The Solution
Here's the corrected code:
Javascript
Fixed
// Solution 1: Basic async/await syntax
async function fetchUserData(userId) {
try {
console.log("Fetching user...");
const user = await getUserById(userId);
console.log("User fetched:", user.name);
return user;
} catch (error) {
console.error("Failed to fetch user:", error.message);
throw error;
}
}
function getUserById(id) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (id > 0) {
resolve({ id, name: `User ${id}`, email: `user${id}@example.com` });
} else {
reject(new Error("Invalid user ID"));
}
}, 1000);
});
}
// Usage
async function main() {
try {
const user = await fetchUserData(123);
console.log("Got user:", user);
} catch (error) {
console.error("Main error:", error.message);
}
}
main();
// Solution 2: Sequential async operations
async function processUserDataSequentially(userId) {
try {
// Each operation waits for the previous to complete
const user = await fetchUserData(userId);
const posts = await fetchUserPosts(user.id);
const processedPosts = await processAllPosts(posts);
return {
user,
postsCount: processedPosts.length,
posts: processedPosts
};
} catch (error) {
console.error("Sequential processing failed:", error);
throw error;
}
}
function fetchUserPosts(userId) {
return new Promise((resolve) => {
setTimeout(() => {
const posts = [
{ id: 1, title: "Post 1", content: "Content 1" },
{ id: 2, title: "Post 2", content: "Content 2" }
];
resolve(posts);
}, 800);
});
}
function processAllPosts(posts) {
return new Promise((resolve) => {
setTimeout(() => {
const processed = posts.map(post => ({
...post,
processed: true,
wordCount: post.content.split(" ").length
}));
resolve(processed);
}, 500);
});
}
// Solution 3: Parallel async operations
async function processUserDataParallel(userId) {
try {
const user = await fetchUserData(userId);
// Run these operations in parallel
const [posts, preferences, activity] = await Promise.all([
fetchUserPosts(user.id),
fetchUserPreferences(user.id),
fetchUserActivity(user.id)
]);
return {
user,
posts,
preferences,
activity
};
} catch (error) {
console.error("Parallel processing failed:", error);
throw error;
}
}
function fetchUserPreferences(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ theme: "dark", notifications: true });
}, 600);
});
}
function fetchUserActivity(userId) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ lastLogin: new Date(), loginCount: 42 });
}, 400);
});
}
// Solution 4: Error handling patterns
async function robustDataFetching(userId) {
let user = null;
let posts = [];
let preferences = {};
try {
// Critical operation - must succeed
user = await fetchUserData(userId);
} catch (error) {
console.error("Critical error - cannot continue:", error);
throw error;
}
try {
// Optional operation - can fail gracefully
posts = await fetchUserPosts(user.id);
} catch (error) {
console.warn("Posts fetch failed, using empty array:", error);
posts = [];
}
try {
// Another optional operation
preferences = await fetchUserPreferences(user.id);
} catch (error) {
console.warn("Preferences fetch failed, using defaults:", error);
preferences = { theme: "light", notifications: false };
}
return { user, posts, preferences };
}
// Solution 5: Async iteration
async function processUsersInBatches(userIds) {
const results = [];
for (const userId of userIds) {
try {
console.log(`Processing user ${userId}...`);
const userData = await processUserDataSequentially(userId);
results.push(userData);
// Add delay between requests
await new Promise(resolve => setTimeout(resolve, 100));
} catch (error) {
console.error(`Failed to process user ${userId}:`, error);
results.push({ error: error.message, userId });
}
}
return results;
}
// Solution 6: Async function expressions and arrows
const asyncArrowFunction = async (data) => {
const result = await processData(data);
return result;
};
const asyncFunctionExpression = async function(data) {
try {
return await processData(data);
} catch (error) {
console.error("Processing failed:", error);
return null;
}
};
function processData(data) {
return new Promise((resolve) => {
setTimeout(() => resolve(`Processed: ${data}`), 300);
});
}
// Solution 7: Practical examples
async function initializeApp() {
try {
console.log("Initializing app...");
const config = await loadAppConfig();
const user = await authenticateUser();
const permissions = await loadUserPermissions(user.id);
console.log("App initialized successfully");
return { config, user, permissions };
} catch (error) {
console.error("App initialization failed:", error);
throw error;
}
}
function loadAppConfig() {
return Promise.resolve({ version: "1.0", apiUrl: "/api" });
}
function authenticateUser() {
return Promise.resolve({ id: 1, name: "Test User" });
}
function loadUserPermissions(userId) {
return Promise.resolve(["read", "write"]);
}
// Run examples
processUserDataSequentially(123)
.then(result => console.log("Sequential result:", result))
.catch(console.error);
processUserDataParallel(123)
.then(result => console.log("Parallel result:", result))
.catch(console.error);
robustDataFetching(123)
.then(result => console.log("Robust result:", result))
.catch(console.error);
processUsersInBatches([1, 2, 3])
.then(results => console.log("Batch results:", results))
.catch(console.error);
initializeApp()
.then(result => console.log("App initialized:", result))
.catch(console.error);
Key Takeaways
Use async/await for cleaner asynchronous code. Always wrap await calls in try-catch blocks. Use Promise.all() with await for parallel operations. Async functions automatically return promises.