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.