Learn by reading through in order

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
What Each of the Two awaits Waits For
The caller — const message = await showParking()
  • showParking() runs until the first await inside it, then returns a pending Promise
  • Execution doesn't reach console.log(message) until that Promise fulfills
Inside async function showParking()
  • Prints Looking for an open spot
  • await assignSpot() — pauses until it fulfills 100ms later
  • Moves to the next line once spot holds "B2-17"
  • The returned message fulfills the Promise it returned
The inner one waits for assignSpot's result; the outer one waits for the function to finish. The returned value ends up in message.

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.

Send a booking notification for a hair salon. delay, confirmBooking, and registerReminder are already declared.

① Rewrite notifyBooking as a function that can wait for results inside it.

② Wait for the confirmed date and time, then print “Your appointment: Sep 14, 15:00”.

③ Register a reminder for that date and time, then print the result.

④ Outside the function, wait for it to finish, then print “Your booking is complete”.

(If it runs correctly, an explanation will appear.)

JavaScript / TypeScript Editor

Run code to see output

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
}
Which Line Runs Next on Success and Failure
If the paymentsucceedsawait payByCard()finishesOn to sendReceipton the next lineSends thereceiptIf the cardhas expiredawait payByCard()line throwsSkips the restof trycatch (error)prints the message
The top row moves on to the next line; the bottom row skips the rest of try and jumps to catch. Where execution goes depends on the state of the awaited Promise.

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.

Renew books borrowed from a library. delay, loans, and renewLoan are already declared.

① In requestRenewal, wait for the result of renewLoan and print the message you get.

② If it fails, print “Can't renew: ” followed by the Error's message.

③ Whether it succeeds or fails, finish by printing a line like “Finished processing Intro to Statistics”.

④ Wait until the first book in loans is done before requesting the second.

JavaScript / TypeScript Editor

Run code to see output

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)
When Results Arrive, Serial vs Parallel
Serial 0mscall stock120ms stock incall reviews170ms reviews incall date260msall 3 are inParallel 0mscall all 350msreviews arrive90msdate arrives120ms stock inall 3 are in
In the bottom row, all three start at 0ms and are in by 120ms, the slowest one. With one await per line, the total wait is 260ms.

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.

SituationHow to write itWhy
Loading the stock count, review count, and delivery dateawait Promise.all (parallel)None of them uses another's result, so they can start together
Sending a confirmation email after getting the order numberOne await per line (serial)The second call needs the first call's result
Processing an array's elements one at a time, in orderawait in the body of for...of (serial)In parallel, elements can finish in a different order than the array

A weather app loads forecasts for three cities. Compare the order in which they arrive. delay, loadForecast, and cities are already declared.

① Going through cities in order, wait for each forecast to arrive before loading the next city.

② Print “Loaded one city at a time”.

③ This time, start loading all three cities at once and wait until they're all in.

④ Print the forecasts joined with “ / ”.

JavaScript / TypeScript Editor

Run code to see output

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
points Stays a Promise
points =loadPoints()Holds a pendingPromiseFulfillsafter 100mspoints stillholds the PromiseEmbeddedin a stringPrints[object Promise]
Even after it fulfills, points is never replaced with 1200. To get the value, you need await.

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.

Check whether coupons have expired and tell the user which ones can be used. delay, coupons, and isExpired are already declared.

① Without await, call isExpired with the first coupon and use the result as an if condition.

② If the condition holds, print “AUTUMN10 has expired”; otherwise, print “AUTUMN10 is ready to use”.

③ Loop over coupons in order, wait for each result, then run the same check and print the message.

JavaScript / TypeScript Editor

Run code to see output
QUIZ

Knowledge Check

Answer each question one by one.

Q1Inside try, what happens to the lines below a line that awaits a Promise which gets rejected?

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?