async/await doesn't introduce a new asynchronous mechanism — it's syntax sugar over Promises that makes asynchronous code read top-to-bottom like synchronous code.
async function loadUser(id) {
const res = await fetch(`/api/users/${id}`); // pauses THIS function until the Promise settles
const user = await res.json();
return user; // an async function always returns a Promise, wrapping this return value
}
await only pauses the async function it's inside — it does not block the rest of the program, since the JavaScript engine is free to run other code (other event handlers, other Promise callbacks) while this function is paused waiting.
awaitasync function loadData() {
await fetch("/api/data");
console.log("data loaded");
}
loadData();
console.log("this runs first"); // logs before "data loaded", regardless of fetch speed
Calling an async function without await-ing it doesn't pause the caller — execution in the caller continues immediately, and whatever's inside the async function resumes later. This is correct behavior, not a bug, but it's a frequent source of "why did this log out of order" confusion.
await where concurrent would do// Slow — the second fetch doesn't even START until the first one fully finishes
async function loadBoth(userId, postId) {
const user = await fetchUser(userId);
const post = await fetchPost(postId);
return { user, post };
}
// Fast — both requests fire at the same time, total time ≈ the slower of the two, not the sum
async function loadBothFast(userId, postId) {
const [user, post] = await Promise.all([fetchUser(userId), fetchPost(postId)]);
return { user, post };
}
Two independent await calls in sequence run one after another even though nothing requires it — a very common, easy-to-miss performance bug. Use Promise.all whenever the requests don't actually depend on each other's results.
try/catchasync function loadUser(id) {
try {
const res = await fetch(`/api/users/${id}`);
if (!res.ok) throw new Error(`Request failed: ${res.status}`);
return await res.json();
} catch (err) {
console.error("Could not load user:", err);
throw err; // re-throw if the caller also needs to know
}
}
A rejected awaited Promise throws inside the async function exactly like a synchronous throw would, which is why ordinary try/catch works for async errors — no special async-specific error-handling syntax is needed.