Async Code That Reads Like Sync Code
JavaScript async/await
`async` functions and the `await` keyword let you write async code in a straight-line, top-to-bottom style — much easier to read than `.then` chains.
What you'll learn
- Mark a function `async`
- `await` a Promise
- Handle errors with `try`/`catch`
async/await is syntactic sugar over Promises. Mark a function
async to use await inside it. The result is async code that reads
like ordinary, top-to-bottom code.
function delay(ms) {
return new Promise((r) => setTimeout(r, ms));
}
async function run() {
console.log("start");
await delay(50);
console.log("after 50ms");
await delay(50);
console.log("after 100ms");
}
run(); await pauses the function until the Promise resolves, then
continues with the resolved value.
Returning From an async Function
An async function always returns a Promise. Whatever you return
becomes the resolved value. Throwing becomes a rejection.
async function getNumber() {
return 42;
}
console.log(getNumber()); // Promise { 42 }
getNumber().then(console.log); // 42 await Unwraps Promises
If you await a non-Promise, it’s used as-is. If you await a
Promise, you get its resolved value.
async function example() {
const a = await 42; // 42 — non-Promise passes through
const b = await Promise.resolve("hi");
return [a, b];
}
example().then(console.log); // [42, "hi"] Error Handling With try/catch
A rejected Promise becomes a thrown error inside an async function.
Catch it with try/catch — same as synchronous code.
async function run() {
try {
const result = await Promise.reject(new Error("boom"));
console.log(result);
} catch (e) {
console.log("caught:", e.message); // "caught: boom"
} finally {
console.log("cleanup");
}
}
run(); This is the killer feature. With .then/.catch, error flow is
implicit. With async/await, it’s the same try/catch you’ve
been using since the start.
Parallel vs Sequential
await waits. If you await two independent things one after
another, they run sequentially.
function fetchSlow(label, ms) {
return new Promise((r) => setTimeout(() => r(label), ms));
}
async function sequential() {
const a = await fetchSlow("a", 100); // wait 100ms
const b = await fetchSlow("b", 100); // then wait another 100ms
return [a, b]; // ~200ms total
}
sequential().then(console.log); For independent work, kick off the Promises first, then await them:
function fetchSlow(label, ms) {
return new Promise((r) => setTimeout(() => r(label), ms));
}
async function parallel() {
const ap = fetchSlow("a", 100); // started
const bp = fetchSlow("b", 100); // started, in parallel
return [await ap, await bp]; // ~100ms total
}
parallel().then(console.log); Or use Promise.all:
function fetchSlow(label, ms) {
return new Promise((r) => setTimeout(() => r(label), ms));
}
async function withAll() {
const [a, b] = await Promise.all([
fetchSlow("a", 100),
fetchSlow("b", 100),
]);
return [a, b]; // ~100ms total
}
withAll().then(console.log); Top-Level await
Inside a module, you can await at the top level — no async
wrapper needed.
const response = await fetch("/api/data");
export const data = await response.json(); Other modules that import from data.js will wait for its setup
before running.
Try It Yourself
Exercise
Rewrite a Chain With async/await
function fetchUser(id) {
return Promise.resolve({ id, name: "Ada" });
}
async function loadGreeting() {
// your code
}
✅ Show solution
async function loadGreeting() {
try {
const u = await fetchUser(1);
const g = `Hello, ${u.name}`;
return g.toUpperCase();
} catch {
console.log("oops");
}
}
Up Next
The most common async operation in web development: making an HTTP
request with fetch.