Academic Block

Async / Await Get Certified

async/await is modern JavaScript’s way of writing asynchronous code that still looks and feels like ordinary sequential code. It is built on top of Promises and makes complex flows much easier to read and maintain.

The async Keyword

Adding the async keyword before a function declaration turns that function into an asynchronous function. An async function always returns a Promise.

What this code does: Declares a simple async function that returns a value. The returned value is automatically wrapped in a resolved Promise.

async function greet() {
    return "Hello from async!";
}

console.log("Calling async function...");

greet().then(function (message) {
    console.log(message);
});

console.log("Handler attached");

The await Keyword

Inside an async function you can use await. It pauses the execution of that function until the Promise on its right-hand side settles, then returns the resolved value.

What this code does: Uses await to wait for a delayed Promise and then logs the result. Notice how the code reads almost like synchronous code.

function delay(value, ms) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(value);
        }, ms);
    });
}

async function run() {
    console.log("Waiting...");
    const result = await delay("Done after delay", 100);
    console.log(result);
}

console.log("Starting...");
run();
console.log("run() called (async continues in background)");

Error Handling with try…catch

When a Promise rejects, await throws the rejection reason. You catch it with a normal try...catch block.

What this code does: Demonstrates catching a rejected Promise with try...catch inside an async function.

function checkAge(age) {
    return new Promise(function (resolve, reject) {
        if (age >= 18) {
            resolve("Access granted");
        } else {
            reject("Access denied: too young");
        }
    });
}

async function verify() {
    try {
        const result = await checkAge(15);
        console.log(result);
    } catch (error) {
        console.log("Error:", error);
    }
}

console.log("Verifying age...");
verify();
console.log("verify() started");

Using finally With try…catch

Just like promise chains, a try...catch block can include a finally clause that always runs, whether an error occurred or not.

What this code does: Demonstrates a full try...catch...finally structure around an await. The finally block always runs.

function loadData() {
    return Promise.resolve("Data loaded");
}

async function run() {
    try {
        const data = await loadData();
        console.log(data);
    } catch (error) {
        console.log("Error:", error);
    } finally {
        console.log("Loading finished");
    }
}

console.log("Starting load...");
run();
console.log("run() called");

An async Function Always Returns a Promise

Because an async function returns a promise, you can still call .then() and .catch() on it, just like any other promise-returning function.

What this code does: An async function that simply returns a number can still be consumed with classic .then() / .catch().

async function getScore() {
    return 90;
}

console.log("Getting score...");

getScore()
    .then(function (score) {
        console.log("Score:", score);
    })
    .catch(function (error) {
        console.log("Error:", error);
    });

console.log("Handler attached");

Sequential vs. Parallel Await

Awaiting promises one after another runs them sequentially, even if they do not depend on each other. This can waste time.

What this code does: Awaits two independent delays one after the other (sequential). Total wait time is the sum of both delays.

function delay(value, ms) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(value);
        }, ms);
    });
}

async function sequential() {
    console.log("Sequential start");
    const a = await delay("A", 80);
    const b = await delay("B", 80);
    console.log(a, b);
    console.log("Sequential finished");
}

sequential();
console.log("sequential() called");

Running Promises in Parallel

When the operations do not depend on each other, start them all first, then await them together with Promise.all().

What this code does: Starts two delays at the same time and waits for both with Promise.all(), so the total wait is only as long as the slower one.

function delay(value, ms) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(value);
        }, ms);
    });
}

async function parallel() {
    console.log("Parallel start");
    const promiseA = delay("A", 80);
    const promiseB = delay("B", 80);
    const results = await Promise.all([promiseA, promiseB]);
    console.log(results[0], results[1]);
    console.log("Parallel finished");
}

parallel();
console.log("parallel() called");

await Inside Loops

Using await inside a for loop processes items one at a time, waiting for each to finish before starting the next.

What this code does: Uses a for...of loop with await to process each item sequentially.

function delay(value, ms) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(value);
        }, ms);
    });
}

async function processItems() {
    const items = ["first", "second", "third"];
    console.log("Processing items...");
    for (const item of items) {
        const result = await delay(item, 60);
        console.log("Processed:", result);
    }
    console.log("All items processed");
}

processItems();
console.log("processItems() called");

Common Mistakes with Async/Await

  • Forgetting the async keyword before a function that uses await.
  • Forgetting to wrap await calls in try...catch, leaving rejected promises unhandled.
  • Awaiting promises one by one when they could run in parallel with Promise.all().
  • Forgetting that an async function always returns a promise, even for a plain return value.
  • Using await outside of an async function (outside of supported top-level await contexts).

What this code does: Shows what happens when an async function rejects and no try...catch is present inside it — the rejection must be handled by the caller with .catch().

// Missing try...catch: an unhandled rejection

async function risky() {
    const result = await Promise.reject("Something failed");
    console.log(result);
}

console.log("Calling risky()...");
risky().catch(function (error) {
    console.log("Caught outside:", error);
});
console.log("Handler attached");

Async/Await vs. Promise Chains

Async/await is built on top of promises, not a replacement for them. The same operation can be written either way.

What this code does: Compares the same data-fetch expressed once as a classic Promise chain and once with async/await.

function getUser() {
    return Promise.resolve({ name: "Michael" });
}

// Using a promise chain:
getUser().then(function (user) {
    console.log("Chain:", user.name);
});

// Using async/await:
async function showUser() {
    const user = await getUser();
    console.log("Async/Await:", user.name);
}

showUser();
console.log("Both styles started");

Comparison Table

Feature Promise Chains Async/Await
Reads like synchronous code? No Yes
Error handling .catch() try…catch
Cleanup step .finally() finally block
Built on top of Promises Promises
Running tasks in parallel Promise.all() Promise.all() with await

Best Practices for Async/Await

  • Always wrap await calls in try...catch when errors are possible.
  • Start independent promises before awaiting them, then use Promise.all() to run them in parallel.
  • Only use await inside an async function.
  • Remember that an async function always returns a promise, so callers can still use .then() and .catch().
  • Avoid unnecessary sequential await calls when the operations do not depend on each other.

Quick Summary

  • async functions always return a promise.
  • await pauses execution until the promise on its right settles.
  • await can only be used inside an async function.
  • Errors from a rejected await are caught with try...catch.
  • A finally block runs regardless of success or failure.
  • Awaiting promises one by one runs them sequentially; use Promise.all() to run independent promises in parallel.
  • Async/await is built on top of promises, not a replacement for them.

Try It Yourself

Modify the function below. Try changing the delay times, causing an error to see the catch block run, or switching the sequential awaits to Promise.all().

What this code does: A complete practice function that logs sequential results, can throw to exercise the catch block, and always runs finally when finished.

function delay(value, ms) {
    return new Promise(function (resolve) {
        setTimeout(function () {
            resolve(value);
        }, ms);
    });
}

async function main() {
    try {
        console.log("Starting...");
        const first = await delay("First result", 80);
        console.log(first);
        const second = await delay("Second result", 80);
        console.log(second);
    } catch (error) {
        console.log("Error:", error);
    } finally {
        console.log("All done");
    }
}

main();
console.log("main() called");
Tip: Think of await as “pause here until this promise settles.” Use try...catch for errors, start independent work before awaiting it, and reach for Promise.all() whenever tasks can run at the same time instead of one after another.

Understanding async and await completes the picture started with promise chaining and static Promise methods, and prepares you for real-world asynchronous JavaScript such as fetching data from APIs.

🧪 Test Your JavaScript Code

Edit the code on the left and click “Run Code” to see the console output on the right.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close