Promises & async/await
Learn why JavaScript needs async code, how Promises track work in progress, and how async/await makes the whole thing read like a story.
Imagine ordering a coffee. You don't stand frozen at the counter until it's ready — you step aside, the barista makes it, and they call your name when it's done. That's exactly how JavaScript handles slow work: instead of grinding to a halt waiting for a network request or a timer, it starts the task, moves on, and comes back when the result is ready. This pattern is called asynchronous programming, and mastering it unlocks the most powerful, real-world parts of JavaScript.
#Why JavaScript Can't Just Wait
JavaScript runs on a single thread — picture one chef in a kitchen. If that chef stops everything to wait for the oven timer, nobody gets any food. Browsers work the same way: if your code blocks the thread (say, by waiting for a server response), the entire page freezes — buttons stop responding, animations stall, and users leave.
The solution: hand slow tasks off to the browser's built-in helpers (network, timers, etc.) and only handle the result when it arrives, without ever stopping the main thread.
The Ticket Stub Analogy
A Promise is like a ticket stub at a coat check. The moment you hand over your coat, you get a stub — a placeholder for the coat you'll pick up later. The coat isn't in your hands yet, but you have proof the work is in progress. When you come back, either the coat is ready (fulfilled) or it was lost (rejected). The stub is what you pass around in the meantime.
#The Three States of a Promise
Every Promise lives in exactly one of three states: - Pending — the async work is still in progress. - Fulfilled — the work completed successfully and produced a value. - Rejected — something went wrong and produced an error.
Once a Promise moves from pending to either state, it never changes again.
// A Promise that resolves after 1 second
const waitOneSecond = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("Time's up!");
}, 1000);
});
// Consuming it with .then and .catch
waitOneSecond
.then((message) => console.log(message))
.catch((error) => console.error(error));#async/await: The Cleaner Syntax
.then chains work, but they can get messy. async/await is syntactic sugar on top of Promises — the same mechanism, but written like normal top-to-bottom code. Two rules: 1. Mark a function with async to allow await inside it. 2. await pauses only that function until the Promise settles — the rest of the app keeps running.
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function greetAfterDelay() {
console.log("Waiting...");
await delay(1000); // pauses here, not the whole app
console.log("Hello!");
}
greetAfterDelay();async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error: ${response.status}`);
}
const data = await response.json();
console.log(data);
} catch (error) {
console.error("Something went wrong:", error.message);
}
}
fetchData("https://jsonplaceholder.typicode.com/todos/1");await only works inside an async function
You cannot use await at the top level of a regular script. If you try, JavaScript throws a syntax error. The fix is to wrap the code in an async function: ``js async function main() { const result = await somePromise; } main(); ``
Run independent tasks in parallel with Promise.all
Awaiting Promises one by one runs them in series. For independent tasks, use Promise.all to run them in parallel — much faster: ``js const [users, posts] = await Promise.all([fetchUsers(), fetchPosts()]); ``
What does `await` do when placed in front of a Promise?
Key takeaways
- JavaScript is single-threaded, so async patterns let slow tasks run without freezing the page.
- A Promise has three states: pending, fulfilled, and rejected — and it never goes back.
- Use .then/.catch to chain Promise handling, or async/await for cleaner, readable code.
- Always wrap awaited code in try/catch to handle rejections gracefully.
- Use Promise.all to run independent async tasks in parallel instead of one at a time.
The lesson explains that await pauses only the current async function, not the whole app. Given this code, what gets logged and in what order?
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function run() {
console.log("A");
await delay(0);
console.log("C");
}
run();
console.log("B");This code has a bug — what's wrong?
function main() {
const message = await delay(1000);
console.log(message);
}
main();Complete this async function. It should pause until the Promise settles, then handle any rejection gracefully. Fill in the keyword that pauses on a Promise, the keyword that marks the function, and the block that catches errors.
function loadData() { try { const data = fetchData(); console.log(data); } (error) { console.error(error.message); } }
Put the lines in order to build a Promise that resolves after one second and log its value. This mirrors the lesson's waitOneSecond example.
resolve("Time's up!");const waitOneSecond = new Promise((resolve, reject) => {});
}, 1000);
setTimeout(() => {waitOneSecond.then((message) => console.log(message));
Write an async function called getUserName that: 1. Uses fetch to GET https://jsonplaceholder.typicode.com/users/1 2. Parses the JSON response 3. Returns the user's name property 4. Catches any errors and logs them with console.error
Then call the function and log the returned name.
Try it live — edit the code and hit Run to see the output: