Q1Inside try, what happens to the lines below a line that awaits a Promise which gets rejected?
async / await — Waiting for Promise Results Line by Line
Wait for a Promise's result with async / await, catch failures with try / catch, compare serial waits with Promise.all, and see what a missing await does.
With a Promise's then, the value only reaches you as an argument to a function. To use the first result in a third step, you have to nest functions or copy the value into an outer variable, which makes the code harder to read.
This article covers async / await, which waits for a result and then moves on to the next line, along with ways to wait for several Promises.
Writing Steps from Top to Bottom — async Functions and await
Say you reserve an open spot in a parking lot and put the assigned spot number into a message. With then, everything that uses the spot number has to go inside the function you pass to then, and any code written below it, outside that function, runs before the number arrives.
Inside an async function (a function declared with async that always returns a Promise when called), you can use await. await pauses only the rest of that function until the result is settled, then unwraps the fulfilled value. While it's paused, the caller gets a pending Promise back right away.
// Returns a Promise that fulfills with value after ms milliseconds (same as the previous article)
function delay(ms, value) {
return new Promise((resolve) => {
setTimeout(() => resolve(value), ms);
});
}
const assignSpot = () => delay(100, "B2-17");
// Write await inside a function marked async
async function showParking() {
console.log("Looking for an open spot"); // Looking for an open spot
const spot = await assignSpot(); // Doesn't move to the next line until fulfilled
console.log(`Reserved spot ${spot}`); // Reserved spot B2-17
return `You can park in spot ${spot}`; // Fulfills the Promise returned earlier with this message
}
// The caller also waits for the returned Promise with await
const message = await showParking();
console.log(message); // You can park in spot B2-17
showParking()runs until the firstawaitinside it, then returns a pending Promise- Execution doesn't reach
console.log(message)until that Promise fulfills
- Prints
Looking for an open spot await assignSpot()— pauses until it fulfills 100ms later- Moves to the next line once
spotholds"B2-17" - The returned message fulfills the Promise it returned
An async function with no return still returns a Promise, which fulfills with undefined once the body reaches its end. Even when you don't need a value, putting await on the call makes the next line wait until the work inside has finished.
await Outside a Function Only Works in Some Places
The runtime on this page wraps your whole code in an async function, so you can write await outside a function too, as in const message = await showParking();. In a regular JavaScript file, await outside an async function can be a SyntaxError, so when you write code elsewhere, keep your awaits inside async functions.
Treating Failures as Exceptions — await and try / catch
When you hold a seat for a concert and then pay by card, the payment can fail. Pulling values out with await doesn't build a chain, so you can't catch the failure with a .catch at the end the way the previous article did.
If you throw inside an async function, the Promise that function returns is rejected. When you await a rejected Promise, the same Error is thrown on that await line, so you can simply wrap it in try / catch.
// delay is the same as in the first section's code
const holdSeat = () => delay(100, "S-204");
const sendReceipt = (seatId) => delay(100, `Sent the receipt for ${seatId}`);
// Throwing inside an async function rejects the Promise it returns
async function payByCard() {
await delay(100); // Just waits 100ms without using a value
throw new Error("The card has expired");
}
try {
const seatId = await holdSeat();
console.log(`Held seat ${seatId}`); // Held seat S-204
await payByCard(); // It's rejected, so this line throws
const receipt = await sendReceipt(seatId); // Nothing from here down runs
console.log(receipt);
} catch (error) {
console.log(`Purchase failed: ${error.message}`); // Purchase failed: The card has expired
}
catch only receives exceptions thrown inside the braces of try, so put any await line that might fail inside try. Cleanup that should run whether the call succeeds or fails goes in a finally block after try / catch.
Starting Three Calls at Once — Serial vs Promise.all
Say a product page loads the stock count, the number of reviews, and the estimated delivery date when it opens. If you write one await per line, each request waits for the previous one to finish before it starts, even though no line uses the previous result. Requests that have nothing to do with each other end up queued.
Starting the next task only after the previous one finishes is called serial; starting several at once and then waiting is called parallel. To run them in parallel, call all three functions first to get their Promises, pass them to Promise.all from the previous article, and wait with await instead of then.
// delay is the same as in the first section's code
const loadStock = () => delay(120, 8);
const loadReviews = () => delay(50, 23);
const loadArrival = () => delay(90, "Sep 13");
// Serial: call the next one after each arrives (120 + 50 + 90 = 260ms until all are in)
const stock = await loadStock();
const reviews = await loadReviews();
const arrival = await loadArrival();
console.log([stock, reviews, arrival].join(" / ")); // 8 / 23 / Sep 13
// Parallel: call all three first, then wait (all are in after the slowest, 120ms)
const promises = [loadStock(), loadReviews(), loadArrival()];
const results = await Promise.all(promises);
console.log(results.join(" / ")); // 8 / 23 / Sep 13 (same result)
If you write await in the body of for...of, the array's elements are also processed serially, one at a time, and the total wait is the sum for every element. The table below shows when to choose serial and when to choose parallel.
| Situation | How to write it | Why |
|---|---|---|
| Loading the stock count, review count, and delivery date | await Promise.all (parallel) | None of them uses another's result, so they can start together |
| Sending a confirmation email after getting the order number | One await per line (serial) | The second call needs the first call's result |
| Processing an array's elements one at a time, in order | await in the body of for...of (serial) | In parallel, elements can finish in a different order than the array |
Calling Without await — A Promise Ends Up in the Variable
Say you show a member's points balance and offer a perk if they have 1000 points or more. If you forget await when calling an async function, there's no SyntaxError — you just get the wrong text and the wrong comparison result, which makes the mistake hard to track down.
An async function doesn't return the value you return; it returns a Promise that fulfills with that value. If you take the result without await, the variable holds the Promise, and embedding it in a template literal prints [object Promise] (what a Promise looks like when converted to a string).
// delay is the same as in the first section's code
async function loadPoints() {
await delay(100);
return 1200; // Fulfills the Promise returned earlier with 1200
}
// Forget await, and points holds a Promise
const points = loadPoints();
console.log(`Points balance: ${points}`); // Points balance: [object Promise]
console.log(points >= 1000); // false (a Promise becomes NaN as a number, so the comparison fails)
// With await, the variable holds the fulfilled value
const balance = await loadPoints();
console.log(`Points balance: ${balance}`); // Points balance: 1200
console.log(balance >= 1000); // true
If the output shows [object Promise], or a condition that should hold comes out false, check whether the async function call has await. In an if condition, a Promise is an object and therefore truthy, so the condition holds no matter what value the function returns.
A Failure Without await Never Reaches catch
Inside try, if you call an async function that returns a rejected Promise without await, that line doesn't throw and catch never runs. Nothing handles the failure, and the runtime on this page prints nothing at all. To receive it in catch, add await to the call.
Knowledge Check
Answer each question one by one.
Q2If you await three calls that take 70ms, 30ms, and 50ms, one per line, about how long until all three are in?
Q3If you call an async function ending in return 1200; without await and store the result in a variable, what does the variable hold?