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");
async function greet() — marking a function async means it always returns a Promise, even if you return a plain value.return "Hello from async!" — the runtime wraps this string in a resolved Promise automatically.}console.log("Calling async function...")greet() — because the function is async, the caller receives a Promise, not the string itself..then(function (message) { console.log(message); }) — the resolved value (“Hello from async!”) arrives here.console.log("Handler attached") — runs before the then callback because Promise settlement is asynchronous (microtask).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)");
function delay(value, ms)return new Promise(function (resolve) {setTimeout(function () { resolve(value); }, ms)}); }async function run() — can use await inside.console.log("Waiting...")const result = await delay("Done after delay", 100) — execution of run pauses until the Promise resolves; the rest of the program is not blocked.console.log(result) — prints after the 100 ms delay.}console.log("Starting...")run() — returns a Promise immediately; body continues in the background.console.log("run() called (async continues in background)") — proves the caller is not blocked by await inside run.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");
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 { — place await here so a rejection becomes a catchable error.const result = await checkAge(15) — age 15 triggers reject; without try/catch this would be an unhandled rejection.console.log(result)} catch (error) { — receives the rejection reason as a normal exception.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");
function loadData() { return Promise.resolve("Data loaded"); }async function run()try {const data = await loadData()console.log(data)} catch (error) { — ready if loadData ever rejects.console.log("Error:", error)} finally { — always runs after try or catch.console.log("Loading finished") — ideal for hiding spinners or releasing resources.} }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");
async function getScore()return 90 — automatically wrapped in a resolved Promise.}console.log("Getting score...")getScore() — callers can still use classic Promise methods..then(function (score) { console.log("Score:", score); }).catch(function (error) { console.log("Error:", error); })console.log("Handler attached") — async functions are Promises; you can mix await inside and then/catch outside.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");
function delay(value, ms)return new Promise(... setTimeout ... resolve(value) ...)async function sequential()console.log("Sequential start")const a = await delay("A", 80) — waits the full 80 ms before continuing.const b = await delay("B", 80) — only starts after the first finishes. Total wait ≈ 160 ms.console.log(a, b)console.log("Sequential finished")}sequential()console.log("sequential() called") — sequential awaits are simple but slower when tasks are independent.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");
function delay(value, ms)return new Promise(...)async function parallel()console.log("Parallel start")const promiseA = delay("A", 80) — timer starts immediately; we do not await yet.const promiseB = delay("B", 80) — both timers run at the same time.const results = await Promise.all([promiseA, promiseB]) — waits only for the slower of the two (≈ 80 ms total, not 160).console.log(results[0], results[1])console.log("Parallel finished")}parallel()console.log("parallel() called") — key pattern: start independent work first, then await Promise.all.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");
function delay(value, ms)return new Promise(...)async function processItems()const items = ["first", "second", "third"]console.log("Processing items...")for (const item of items) { — loops are fine with await; each iteration waits for the previous one.const result = await delay(item, 60) — processes one item at a time (sequential by design).console.log("Processed:", result)}console.log("All items processed")}processItems()console.log("processItems() called") — for parallel item processing you would map to promises and await Promise.all instead.Common Mistakes with Async/Await
- Forgetting the
asynckeyword before a function that usesawait. - Forgetting to wrap
awaitcalls intry...catch, leaving rejected promises unhandled. - Awaiting promises one by one when they could run in parallel with
Promise.all(). - Forgetting that an
asyncfunction always returns a promise, even for a plain return value. - Using
awaitoutside of anasyncfunction (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");
// Missing try...catch: an unhandled rejectionasync function risky()const result = await Promise.reject("Something failed") — rejection becomes an exception inside the async function.console.log(result)}console.log("Calling risky()...")risky().catch(function (error) { console.log("Caught outside:", error); }) — because risky has no try/catch, the returned Promise rejects and the caller must handle it.console.log("Handler attached") — always handle rejections either inside with try/catch or outside with .catch.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");
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() — same result, reads top-to-bottom like synchronous code.console.log("Async/Await:", user.name)}showUser()console.log("Both styles started") — async/await is syntactic sugar over Promises, not a separate system.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
awaitcalls intry...catchwhen errors are possible. - Start independent promises before awaiting them, then use
Promise.all()to run them in parallel. - Only use
awaitinside anasyncfunction. - Remember that an
asyncfunction always returns a promise, so callers can still use.then()and.catch(). - Avoid unnecessary sequential
awaitcalls when the operations do not depend on each other.
Quick Summary
asyncfunctions always return a promise.awaitpauses execution until the promise on its right settles.awaitcan only be used inside anasyncfunction.- Errors from a rejected
awaitare caught withtry...catch. - A
finallyblock 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");
function delay(value, ms)return new Promise(...)async function main()try {console.log("Starting...")const first = await delay("First result", 80)console.log(first)const second = await delay("Second result", 80) — sequential; try swapping for Promise.all to run in parallel.console.log(second)} catch (error) { — add a throw or a rejecting Promise to see this run.console.log("Error:", error)} finally {console.log("All done")} }main()console.log("main() called")Tip: Think ofawaitas “pause here until this promise settles.” Usetry...catchfor errors, start independent work before awaiting it, and reach forPromise.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.
Click “Run Code” to see the output here.
Code Explanation
The complete example stays visible. Use Previous and Next to move through its logical blocks.
Select Next to begin exploring the code.