Promise chains work, but they read backwards. A sequence of three dependent tasks becomes a staircase of .then() calls, and your eye has to zig-zag down the page to follow the story. Worse, error handling splits between .then() and .catch(), so the happy path and the sad path live in two different places.
Async/await is syntactic sugar over Promises. It lets you write asynchronous code that reads like ordinary synchronous code. An async function can contain await expressions, and each await pauses the function until the Promise settles, then resumes with the resolved value. The result is flatter, left-to-right code where try/catch can handle both success and failure in one place.
Under the hood nothing changes — you are still using Promises. The engine wraps your function's return value in a Promise and schedules the pauses for you. What changes is the shape of the code you read and write.
flowchart LR
C["caller: const p = fetchData()"] -->|"immediately"| P["Promise { pending }"]
P -->|"later: settles"| R["resolved value"]
style P fill:#e0900b,color:#fff
style R fill:#1e7f3d,color:#fff
The two keywords
Write async before a function definition — async function fetchData() — and the function becomes an async function. The body can now use await, and the function always returns a Promise, even if you write a plain value like return 42.
await sits before a Promise — const data = await somePromise — and does two things. First, it pauses the async function, letting other code run. Second, when the Promise resolves, it resumes the function and gives back the resolved value. If the Promise rejects, await throws the rejection as an exception, which you can catch with try/catch.
This pause is local to the async function. The rest of your program — timers, event handlers, other async functions — keeps running. await is cooperative multitasking, not a lock.
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
async function greet() {
const name = await delay(10, "Ada");
console.log("Hello, " + name);
return "done";
}
greet();
console.log("first");
What async really returns
An async function wraps whatever you return in a Promise. Write return 7 and the caller receives a Promise that resolves to 7. This is easy to forget when you call an async function from ordinary code: the value you get back is a Promise object, not the number 7.
To use the result you must either await it from another async function or attach .then() to the Promise. Mixing the two styles is common in real codebases — an async function internally uses await, but a non-async caller uses .then() because it cannot use await.
One more rule: await is only legal inside an async function. Try to use it at the top level of a regular function and JavaScript throws a syntax error. This is why a function that needs to wait on asynchronous work must be declared async.
flowchart TD
S["start greet()"] --> A["await delay(10, 'Ada')"]
A -->|"pause greet()"| O["other code runs<br/>console.log('first')"]
O -->|"delay resolves"| R["resume greet()<br/>console.log('Hello, Ada')"]
style A fill:#e0900b,color:#fff
style O fill:#3776ab,color:#fff
style R fill:#1e7f3d,color:#fff
function delay(ms, value) {
return new Promise((resolve) => setTimeout(() => resolve(value), ms));
}
async function steps() {
const a = await delay(5, 1);
const b = await delay(5, 2);
const c = await delay(5, 3);
console.log(a + b + c);
}
steps();
Sequential vs parallel
When you write await one after another, the steps run sequentially. The second delay does not start until the first one finishes. That is correct when step B needs the result of step A, but wasteful when the two tasks are independent.
If the tasks do not depend on each other, start them both and use Promise.all to wait for both together. We cover Promise.all in a later lesson; for now, remember that a string of await statements is a deliberate choice to serialize work, not a free performance boost.
Also remember that await only pauses the async function it sits in. If two different async functions are running, they interleave freely — one pauses at its await while the other runs to its own await. This is what makes concurrent programming possible without threads.
flowchart TD
Q{"Inside an async function?"} -- Yes --> A["await is legal"]
Q -- No --> E["SyntaxError"]
style A fill:#1e7f3d,color:#fff
style E fill:#b91c1c,color:#fff
async function getNumber() {
return 7;
}
const p = getNumber();
console.log(p instanceof Promise);
console.log(typeof p);
p.then((v) => console.log("resolved:", v));
What does this print? Remember that await pauses only the async function, not the whole program.
async function main() {
console.log("A");
await new Promise((r) => setTimeout(r, 5));
console.log("B");
}
main();
console.log("C");
main() starts, prints A, then hits await and pauses.
While main is paused, the top-level console.log('C') runs.
The console.log('B') happens after the timeout, but this predict_output only captures synchronous output.
Write an async function delayedGreet(name, ms) that waits ms milliseconds using setTimeout wrapped in a Promise, then returns the string "Hello, <name>!".
async function delayedGreet(name, ms) {
// wait ms milliseconds, then return "Hello, <name>!"
}
Wrap setTimeout in a new Promise: new Promise((resolve) => setTimeout(resolve, ms)).
Await that Promise, then return the greeting string.
async function delayedGreet(name, ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
return "Hello, " + name + "!";
}
This function tries to use await but forgets the async keyword, so it throws a syntax error. Fix it by adding the one keyword that makes await legal.
function waitAndDouble(ms, n) {
await new Promise((resolve) => setTimeout(resolve, ms));
return n * 2;
}
await is only legal inside an async function.
Add the async keyword before function.
async function waitAndDouble(ms, n) {
await new Promise((resolve) => setTimeout(resolve, ms));
return n * 2;
}
What does this print? An async function always returns a Promise, even when the return value looks like a plain number.
async function give() {
return 5;
}
const x = give();
console.log(x instanceof Promise);
console.log(typeof x);
An async function wraps the return value in a Promise.
typeof a Promise is "object".
The instanceof check returns a boolean, which console.log prints as true or false.
Write sumAfterDelays(a, b) as an async function that awaits a 5-millisecond delay, then returns a + b. The delay itself is new Promise((resolve) => setTimeout(resolve, 5)).
async function sumAfterDelays(a, b) {
// await a 5ms delay, then return a + b
}
Create the Promise with setTimeout, await it, then return the sum.
Remember to mark the function async.
async function sumAfterDelays(a, b) {
await new Promise((resolve) => setTimeout(resolve, 5));
return a + b;
}
Recap
async functiondeclares a function that can useawaitand always returns a Promise.await promisepauses the async function until the Promise settles, then gives back the resolved value.- The pause is local: other code and other async functions keep running.
- An async function wraps its return value in a Promise — unwrap it with
.then()or anotherawait. awaitoutside an async function is a syntax error.- Sequential
awaitstatements run one after another; usePromise.allwhen tasks are independent.
Next you will learn how to catch and recover when a Promise rejects, both with .catch() and with try/catch around await.