JavaScriptNode.jsTutorialBeginner

    Mastering Async/Await in Node.js: A Guide

    Jan 18, 20265 min read

    Stop writing .then() chains! Learn how to write clean, synchronous-looking asynchronous code using modern JavaScript features supported by the WebFiddle engine.

    The History of Asynchrony#

    JavaScript is single-threaded. This means it can only do one thing at a time. However, modern apps need to:

    1. Read a file.
    2. Query a database.
    3. Fetch from an external API.
    4. Send an email.

    If Node.js paused the entire server while waiting for the email to send, your site would be incredibly slow.

    The Dark Ages: Callbacks

    In 2010, we handled this with callbacks.

    getUser(function(user) {
        getPosts(user.id, function(posts) {
            getComments(posts[0].id, function(comments) {
                // "Callback Hell" or "The Pyramid of Doom"
            });
        });
    });
    

    The Transition: Promises

    ES6 introduced Promises, which allowed chaining .then(). Better, but still verbose.

    The Solution: Async/Await

    Introduced in ES2017 (Node 8+), async/await is just "syntactic sugar" over Promises, but it makes asynchronous code look like modern, synchronous code.


    1. Sequential Execution#

    The most basic pattern. We wait for one thing to finish before starting the next.

    async function processUser(userId) {
        try {
            console.log('Fetching user...');
            const user = await db.getUser(userId); // Pauses here!
            
            console.log('Fetching posts...');
            const posts = await db.getPosts(user.id); // Pauses here!
            
            return posts;
        } catch (error) {
            console.error('Something went wrong:', error);
        }
    }
    

    Pros: Easy to read.
    Cons: Slow if the tasks are independent. If getUser takes 1s and getPosts takes 1s, total time is 2s.


    2. Parallel Execution (The Performance Boost)#

    Often, beginners overuse await. If two tasks don't depend on each other, don't await them one by one!

    async function getDashboardData() {
        // Start both requests immediately!
        const userPromise = db.getUser();
        const statsPromise = db.getStats();
        
        // Wait for BOTH to finish
        const [user, stats] = await Promise.all([userPromise, statsPromise]);
        
        return { user, stats };
    }
    

    Pros: Faster! If getUser takes 1s and getStats takes 1s, total time is roughly 1s (because they run at the same time).


    3. Handling Errors with Promise.allSettled#

    A danger of Promise.all is that if one request fails, everything crashes. In Node.js 12.9+, we got Promise.allSettled.

    const results = await Promise.allSettled([
        api.criticalCall(),
        api.optionalCall()
    ]);
    
    // result[0] might be { status: 'fulfilled', value: ... }
    // result[1] might be { status: 'rejected', reason: ... }
    

    This is perfect for dashboards where you want to show "User Profile" even if the "News Feed" API failed.


    Top-Level Await#

    In modern Node.js (modules) and here in WebFiddle, you can use await outside of functions.

    // index.mjs
    const response = await fetch('https://api.github.com/zen');
    const txt = await response.text();
    console.log(txt);
    

    Summary#

    1. Use async/await for cleaner code.
    2. Use try/catch for error handling (no more .catch() chains).
    3. Use Promise.all() when tasks are independent to speed up your app.
    4. Practice these patterns in the WebFiddle Node.js Sandbox!

    Ready to try it yourself?

    Experience the power of WebAssembly and Node.js directly in your browser. No setup required.