Asynchronous JavaScript · Lesson 26 of 40
async and await in JavaScript
async/await (ES2017) is syntax on top of promises that lets you write asynchronous code top to bottom, like synchronous code, with ordinary try/catch for errors. It's how almost all modern JavaScript – React data loading, Node.js APIs, test suites – handles async work.
In this lesson
- How
asyncfunctions andawaitwork - Error handling with try/catch/finally
- Sequential vs parallel – the biggest performance mistake
- Loops, retries and top-level await
The basics
const wait = (ms, value) => new Promise((r) => setTimeout(() => r(value), ms));
const getUser = (id) => wait(30, { id, name: "Asha" });
const getOrders = (userId) => wait(30, [{ id: 101, total: 1200 }, { id: 102, total: 450 }]);
async function showSpend(userId) {
const user = await getUser(userId); // pause here until it resolves
const orders = await getOrders(user.id);
const total = orders.reduce((sum, o) => sum + o.total, 0);
return `${user.name} spent ₹${total}`; // an async function returns a promise
}
const p = showSpend(7);
console.log(p instanceof Promise);
console.log(await p);
true Asha spent ₹1650
asyncbefore a function makes it always return a promise: a returned value becomes a fulfilled promise; a thrown error becomes a rejected one.awaitpauses this function (not the whole program) until the promise settles, then gives you its value – or throws its error.- In ES modules you can use
awaitat the top level, as the last line above does.
Error handling
const fakeApi = (path) => new Promise((resolve, reject) =>
setTimeout(() => path === "/profile"
? resolve({ name: "Ravi" })
: reject(new Error(`404 Not Found: ${path}`)), 20));
async function loadPage(path) {
console.log("Loading", path);
try {
const data = await fakeApi(path);
console.log("Loaded:", data.name);
} catch (err) {
console.log("Error:", err.message); // rejected await lands here
} finally {
console.log("Hide spinner"); // runs either way
}
}
await loadPage("/profile");
await loadPage("/settings");
Loading /profile Loaded: Ravi Hide spinner Loading /settings Error: 404 Not Found: /settings Hide spinner
Sequential vs parallel
Each await waits for the previous one. If the operations don't depend on each other, start them all first and await them together:
const wait = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms));
const getProfile = () => wait(100, "profile");
const getCourses = () => wait(100, "courses");
const getBadges = () => wait(100, "badges");
let t = Date.now();
const a = await getProfile();
const b = await getCourses();
const c = await getBadges();
const sequential = Date.now() - t;
t = Date.now();
const [x, y, z] = await Promise.all([getProfile(), getCourses(), getBadges()]);
const parallel = Date.now() - t;
console.log(sequential >= 300 ? "sequential: ~300 ms" : sequential);
console.log(parallel < 200 ? "parallel: ~100 ms" : parallel);
sequential: ~300 ms parallel: ~100 ms
Choosing
- Does step 2 need step 1's result?yes → await one after the other
- Independent requests?start all, then await Promise.all
- Some may fail but you want the rest?await Promise.allSettled
await in loops
const wait = (ms, v) => new Promise((r) => setTimeout(() => r(v), ms));
const ids = [3, 1, 2];
// for...of with await: one at a time, in order
for (const id of ids) {
console.log("sequential", await wait(id * 10, id));
}
// map + Promise.all: all at once, results still in input order
const results = await Promise.all(ids.map((id) => wait(id * 10, `item ${id}`)));
console.log(results);
// forEach does NOT wait for async callbacks
ids.forEach(async (id) => { await wait(5); });
console.log("forEach returned immediately");
sequential 3 sequential 1 sequential 2 [ 'item 3', 'item 1', 'item 2' ] forEach returned immediately
Common mistakes
- Using
forEachwith an async callback and expecting it to wait – usefor...oforPromise.all. - Forgetting
await:const user = getUser()gives you a pending promise, not the user. - Calling an async function without handling its rejection – wrap in try/catch or add
.catch.
Retry with backoff
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let calls = 0;
async function flakyRequest() {
calls++;
if (calls < 3) throw new Error(`Attempt ${calls} failed`);
return "Data received";
}
async function retry(fn, attempts = 4, delay = 20) {
for (let i = 1; i <= attempts; i++) {
try {
return await fn();
} catch (err) {
console.log(err.message);
if (i === attempts) throw err;
await sleep(delay * 2 ** (i - 1)); // 20, 40, 80 ms…
}
}
}
console.log(await retry(flakyRequest));
Attempt 1 failed Attempt 2 failed Data received
Note return await fn() inside try: without await, a rejection would escape the try block and skip the retry.
Practice
Try it yourself
Load three "pages" in parallel with
Promise.allSettledand print how many succeeded.Show solution
const load = (ok, v) => new Promise((res, rej) => setTimeout(() => (ok ? res(v) : rej(new Error(v))), 10)); const results = await Promise.allSettled([load(true, "home"), load(false, "admin"), load(true, "blog")]); const ok = results.filter((r) => r.status === "fulfilled").map((r) => r.value); console.log(`${ok.length} of ${results.length} loaded:`, ok.join(", "));2 of 3 loaded: home, blog
Quick check
What does an async function return?
Always a promise.
Does await block the whole program?
No – it pauses only the async function; other code and events keep running.
How do you run three independent requests at once?
await Promise.all([a(), b(), c()]) – start them together, then await.
Summary
- async functions return promises; await unwraps them and throws on rejection.
- Use try/catch/finally for errors; await independent work in parallel with Promise.all.
- for...of awaits in order; forEach doesn't wait; use return await inside try.
Finished this lesson?
Mark it complete to track your progress, then carry on with the next lesson.