cf.completefrontend
Open lab My progress
THE TYPESCRIPT FIELD GUIDE

Promises & async functions

Type promises with Promise<T>, see what async functions give back, keep Promise.all results as tuples, and type timeouts and retries that TypeScript checks.

By the end, you can
  • 01
    Read promise typesTell a Promise<Cart> from a Cart, and see how await, then, and Awaited unwrap one.
  • 02
    Type async functionsKnow that an async function always gives back a promise, and write its return type as Promise<T>.
  • 03
    Combine and limit promisesKeep Promise.all results as a tuple, and type a timeout and a retry helper that work for any promise.

A promise of a cart is not a cart

A shop page loads the visitor's cart from its server. That takes time, so loadCart gives back a promise: an object that stands for a value that arrives later (the Promises lesson covers them). Here loadCart is a small stand-in for the server, so nothing goes over the network. The code forgets one word:

Reading a total from a promise (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function showTotal() {  const cart = loadCart();  //    ^?  console.log(`Total: $${cart.total}`);} showTotal();

Line 1 describes a Cart. Lines 3 to 5 are the stand-in: an async function that gives back a cart with 3 items and a total of 12. Line 8 calls it, and the answer after the ^? on line 9 shows what cart is: Promise<Cart>, a promise that will hold a Cart. So line 10 gets an error, on purpose: Property 'total' does not exist on type 'Promise<Cart>'. In plain words: "you're reading total from the promise, and a promise has no total". When it runs, it prints Total: $undefined.

Add await, which waits for the promise and gives back what's inside (the async & await lesson covers it):

Waiting for the cart firstPop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function showTotal() {  const cart = await loadCart();  //    ^?  console.log(`Total: $${cart.total}`);} showTotal();

Only line 8 changed. The answer on line 9 now says const cart: Cart, line 10 has no error, and the code prints Total: $12. TypeScript keeps track of which values are promises and which are the values inside them, and stops you from mixing them up.

A wrapped gift marked Promise<Cart>, labelled what loadCart() gives you, with no total on the outside. An arrow marked await leads to a shopping cart named cart holding a card total: 12.Promise<Cart>what loadCart() gives youno total on the outsideawaittotal: 12carta Cart, with its total
loadCart() gives you a Promise<Cart> straight away, like a parcel you can't see into yet. await waits for it and unwraps it, so cart is a Cart and cart.total is 12.
Real-life analogyA buzzer at a café

At a busy café you pay, and the barista hands you a buzzer instead of a coffee. The ticket on it says what you ordered. When it buzzes, you take it back to the counter and get your coffee.

In real life: You order a coffee and get a buzzer
In TypeScript: Calling loadCart() gives you a Promise<Cart>
In real life: The buzzer's ticket says "1 latte"
In TypeScript: The type Promise<Cart> says what will arrive
In real life: You can't drink the buzzer
In TypeScript: You can't read total from the promise
In real life: When it buzzes, you swap it for the coffee
In TypeScript: await swaps the promise for the Cart
The main idea

A Promise<Cart> is not a Cart. It's a promise that a cart will arrive. TypeScript writes the type of the value inside the angle brackets, and only await or then gets that value out.

Promise<T> names what arrives later

Promise is a generic type: a type with a slot, written in angle brackets, for another type (the Generic types & interfaces lesson covers them). Promise<Cart> holds a cart, and Promise<number> holds a number. When you make a promise yourself with new Promise, TypeScript needs to be told what goes in the slot:

Saying what a new promise holds (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; const unnamed = new Promise((resolve) => resolve({ items: 3, total: 12 }));//    ^?const named = new Promise<Cart>((resolve) => resolve({ items: 3, total: 12 }));//    ^?const broken = new Promise<Cart>((resolve) => resolve({ items: 3 }));

Line 3 makes a promise and calls resolve, the function that fills it, with a cart. The answer on line 4 shows Promise<unknown>: TypeScript doesn't guess the type from what you pass to resolve. Line 5 writes the type in the brackets, new Promise<Cart>, and the answer on line 6 shows Promise<Cart>. Now resolve only takes a Cart, so line 7 gets an error, on purpose: Argument of type '{ items: number; }' is not assignable to parameter of type 'Cart | PromiseLike<Cart>'. In plain words: the object has no total, so it isn't a Cart. (PromiseLike<Cart> means another promise of a cart, which resolve also accepts.)

A function's return type fills the slot too. Here the stand-in answers after 50 milliseconds, with setTimeout, as a real server would answer after a while:

The type flows through thenPop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; function loadCart(): Promise<Cart> {  return new Promise((resolve) => {    setTimeout(() => resolve({ items: 3, total: 12 }), 50);  });} const total = loadCart().then((cart) => cart.total);//    ^?total.then((value) => console.log(`Total: $${value}`));

Line 3 says loadCart gives back a Promise<Cart>, so the new Promise on line 4 knows it holds a cart without its own brackets. Line 9 calls then (the Promise chaining lesson covers it): its callback gets the Cart, and gives back cart.total, a number. The answer on line 10 shows Promise<number>: whatever a then callback gives back becomes the type of the next promise. Line 11 prints Total: $12.

When you forget await

Forgetting await is the most common mistake with promises. Try the three ways to write line 8 below, and watch what TypeScript says and what the code prints:

Playground: add or remove await
Reading a total from loadCartPop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function showTotal() {  const cart = await loadCart();  console.log(`Total: $${cart.total}`);} showTotal();
Console outputTotal: $12
What happens
cart is aCart
TypeScriptNo errors
Try it yourself

await waits for the promise and gives back what's inside it, so cart is a Cart. Line 9 reads its total, and the code prints Total: $12.

TypeScript checks the code for real for each choice, and the output is what the code prints. Each button changes only line 8. Reset goes back to await loadCart().

With await, all is well. Without it, the error appears on line 9, where the promise is used. And if you write the type you expect on line 8, the error moves to line 8 itself, with Type 'Promise<Cart>' is missing the following properties from type 'Cart': items, total: "you said Cart, but this is a promise, which has neither of a cart's properties".

A forgotten await in a condition is worse, because the code still runs and takes the wrong branch:

A promise in a condition (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
async function isLoggedIn(): Promise<boolean> {  return false;} async function showMenu() {  if (isLoggedIn()) {    console.log("Welcome back");  } else {    console.log("Please log in");  }} showMenu();

Line 1 says isLoggedIn gives back a Promise<boolean>, and line 2 makes it false. Line 6 forgets await, so the if tests the promise, an object, which always counts as true. TypeScript marks it, on purpose: This condition will always return true since this 'Promise<boolean>' is always defined. In plain words: "this is a promise, so the test can never be false; did you mean to await it?". When it runs, it prints Welcome back to a visitor who isn't logged in.

TypeScript catches a forgotten await when you use the promise as if it were the value: reading a property, testing it, or putting it where a Cart must go. It says nothing about a call on its own line, like saveCart();, because starting a promise without waiting for it is allowed. A linter rule such as typescript-eslint's no-floating-promises reports those.

What an async function gives back

An async function always gives back a promise, whatever it returns. TypeScript works out the type for you:

TypeScript wraps an async function's resultPop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; async function loadCart() {//             ^?  return { items: 3, total: 12 };} async function saveCart(cart: Cart) {//             ^?  console.log(`Saved ${cart.items} items`);} saveCart({ items: 3, total: 12 });

Line 5 returns a plain object, but the answer on line 4 shows that loadCart gives back a Promise of that object. Line 8's saveCart returns nothing, and the answer on line 9 shows Promise<void>: a promise that only says "done". Line 13 calls it, and it prints Saved 3 items.

When you write the return type yourself, write the promise, not the value inside it. Both mistakes below are on purpose:

Two wrong return types (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; async function countItems(): number {  return 3;} async function loadCart(): Promise<Cart> {  return { items: 3, total: "12" };}

Line 3 says countItems gives back a number. TypeScript says The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'? In plain words: "callers get a promise, so say Promise<number>". Line 7 gets it right, and now TypeScript checks every return against the type inside. Line 8 returns the total as the text "12", so it gets Type 'string' is not assignable to type 'number'.

The table below shows what TypeScript says for each way of writing an async function. Read each row from left to right:

What an async function gives back
When you writeWhat TypeScript says
async function load() { return 3; }It gives back Promise<number>. TypeScript wraps the type for you.
async function load(): Promise<number>TypeScript checks that every return gives a number.
async function load(): numberAn error: an async function always gives back a promise, so its return type must be Promise<…>.
async function save() { … } with no returnIt gives back Promise<void>: a promise that holds nothing useful.
async function load() { return loadCart(); }It gives back Promise<Cart>, not a promise of a promise. Promises never nest.

The last row matters: returning a promise from an async function doesn't make a promise of a promise. Both await and the type Awaited<T> unwrap all the way down to the value (the ReturnType, Awaited & more lesson covers Awaited):

Awaited gets the value out of a promise typePop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function loadCartAgain() {  return loadCart();} type Again = ReturnType<typeof loadCartAgain>;//   ^?type Loaded = Awaited<Again>;//   ^?

Line 8 returns the promise from loadCart. Line 11 asks for loadCartAgain's return type, and the answer on line 12 is Promise<Cart>, one promise. Line 13 unwraps it with Awaited, and the answer on line 14 is the cart's shape, { items: number; total: number; }. That's the Cart type: TypeScript writes out its properties instead of its name. Awaited is the type of what await would give you.

Promise.all keeps each result in its place

A checkout page needs the user and the cart. Waiting for one and then the other is slow, so the page starts both at once and waits for both with Promise.all (the Promise combinators lesson covers it):

Two requests at once, each with its own typePop out in the code editor (opens in a new tab)TypeScript
type User = { name: string };type Cart = { items: number; total: number }; async function loadUser(): Promise<User> {  return { name: "Asha" };} async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function showCheckout() {  const [user, cart] = await Promise.all([loadUser(), loadCart()]);  //     ^?  //           ^?  console.log(`${user.name} has ${cart.items} items`);} showCheckout();

Lines 4 to 10 are two stand-ins. Line 13 passes a list of two promises to Promise.all, awaits it, and takes the two results apart with destructuring. The answers on lines 14 and 15 show that user is a User and cart is a Cart. TypeScript types what Promise.all gives back as a tuple, a list with a type for each place (the Arrays & tuples lesson covers them): [User, Cart]. Line 16 prints Asha has 3 items.

Left: a crate named results, labelled [User, Cart], holds a User card and a Cart card, under the note: list written inside Promise.all. Right: a crate named results, labelled (User | Cart)[], holds two cards that each say User | Cart, under the note: list saved in a variable first.list written inside Promise.all[User, Cart]UserCartresults0: User1: Carteach place keeps its typelist saved in a variable first(User | Cart)[]User | CartUser | Cartresultsevery item could be either
With the list written inside Promise.all, TypeScript keeps a tuple: place 0 is a User and place 1 is a Cart. With a list saved in a variable first, it only knows each item is a User | Cart.

That only works when TypeScript can see each place. Change how line 13 makes the list, and watch the types of user and cart:

Playground: how the list reaches Promise.all
Loading a user and a cart togetherPop out in the code editor (opens in a new tab)TypeScript
type User = { name: string };type Cart = { items: number; total: number }; async function loadUser(): Promise<User> {  return { name: "Asha" };} async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function showCheckout() {  const requests = [loadUser(), loadCart()] as const;  const [user, cart] = await Promise.all(requests);  console.log(`${user.name} has ${cart.items} items`);} showCheckout();
Console outputAsha has 3 items
What happens
requests is areadonly [Promise<User>, Promise<Cart>]
TypeScriptNo errors
Try it yourself

as const makes the list a tuple (a list with a fixed length and a type for each place), so Promise.all gives back [User, Cart]. user is a User and cart is a Cart.

TypeScript checks the code for real for each choice. The output never changes: only what TypeScript knows does. Each button changes only line 13. Reset goes back to as const.

A plain list saved in a variable is an array, not a tuple: every item is a Promise<User> | Promise<Cart>, so every result is a User | Cart. as const (the as const & satisfies lesson covers it) or a written tuple type keeps the places.

Promise.all fails as soon as one request fails. When a page can live without some of its data, like a list of offers, Promise.allSettled waits for every request and tells you how each one went:

Results that say whether they workedPop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function loadOffers(): Promise<string[]> {  throw new Error("Offers server is down");} async function showPage() {  const [cart, offers] = await Promise.allSettled([loadCart(), loadOffers()]);  if (cart.status === "fulfilled") console.log(`Total: $${cart.value.total}`);  if (offers.status === "rejected") {    const reason = offers.reason;    //    ^?    console.log(`No offers: ${reason.message}`);  }} showPage();

Line 8 makes the offers request fail. Line 12 waits for both. Each result has a status: "fulfilled" with a value, or "rejected" with a reason. Its type is a union of the two, and checking status narrows it (the Discriminated unions lesson covers this). So line 13 can read cart.value.total only after checking status; without the check, TypeScript says Property 'value' does not exist on type 'PromiseSettledResult<Cart>'. The answer on line 16 shows the reason is any: TypeScript doesn't know what a failed promise holds. The code prints Total: $12, then No offers: Offers server is down.

The table below compares the four ways to wait for several promises, for a user request and a cart request. Read each row from left to right:

Waiting for several promises
What you callWhat you get backWhat happens when one fails
Promise.all[User, Cart]: each result in its own place.It rejects with the first error, and you get no results.
Promise.allSettled[PromiseSettledResult<User>, PromiseSettledResult<Cart>]: each result says if it worked.It never rejects. Each failed result holds its reason.
Promise.raceUser | Cart: the first one to finish.It fails if the first one to finish failed.
Promise.anyUser | Cart: the first one that worked.It rejects only if every request fails, with an AggregateError.

Typing timeouts and retries

Pages wait for things: a pause before saving, a request that takes too long, a server that fails once and works the next time (the Async patterns lesson builds these in JavaScript). Typing them well starts with a small helper that waits:

A pause that holds nothingPop out in the code editor (opens in a new tab)TypeScript
function wait(ms: number) {//       ^?  return new Promise((resolve) => setTimeout(resolve, ms));} function pause(ms: number): Promise<void> {//       ^?  return new Promise((resolve) => setTimeout(resolve, ms));} async function showSaved() {  await pause(50);  console.log("Saved");} showSaved();

Line 3 makes a promise that setTimeout fills after ms milliseconds. With no return type, the answer on line 2 shows wait gives back Promise<unknown>. Line 6 says what the pause really holds, Promise<void>: nothing to use, only "done". The answer on line 7 shows it. Line 12 waits 50 milliseconds, and line 13 prints Saved.

setTimeout itself gives back an id that clearTimeout uses to cancel the timer, as a page does when it saves a draft only after the typing stops:

A timer's id is a number in the browserPop out in the code editor (opens in a new tab)TypeScript
let timer = setTimeout(() => console.log("Saving draft"), 50);//  ^?clearTimeout(timer);timer = setTimeout(() => console.log("Saved"), 50);

The answer on line 2 shows timer is a number, from TypeScript's browser types. Line 3 cancels the first timer, so only line 4's timer runs, and it prints Saved. In code for Node.js, its types say setTimeout gives back a NodeJS.Timeout object instead. ReturnType<typeof setTimeout> is the type of the id in both places.

Now a timeout: give a request 100 milliseconds, and give up after that. Promise.race settles like whichever promise settles first, so race the request against a promise that rejects after the time is up. The first try has a type error, on purpose:

A timeout that loses the type (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
function timeout(ms: number) {  return new Promise((_, reject) => {    setTimeout(() => reject(new Error("Timed out")), ms);  });} function withTimeout<T>(task: Promise<T>, ms: number): Promise<T> {  return Promise.race([task, timeout(ms)]);}

Lines 1 to 5 make a promise that rejects with an error after ms milliseconds. It has no return type, so it's a Promise<unknown>. Line 7 says withTimeout works for any promise: T is a type parameter, filled in by each call (the Generic functions lesson covers them). On line 8, the race gives a promise of T | unknown, which is just unknown, so TypeScript says Type 'Promise<unknown>' is not assignable to type 'Promise<T>': "you promised a T, but this could hold anything".

The fix is to say what the timeout really gives back: nothing, ever. It only rejects. That's the type never (the any, unknown & never lesson covers it):

A timeout that never gives a valuePop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; function timeout(ms: number): Promise<never> {  return new Promise((_, reject) => {    setTimeout(() => reject(new Error("Timed out")), ms);  });} function withTimeout<T>(task: Promise<T>, ms: number): Promise<T> {  return Promise.race([task, timeout(ms)]);} function loadCart(delay: number): Promise<Cart> {  return new Promise((resolve) => {    setTimeout(() => resolve({ items: 3, total: 12 }), delay);  });} withTimeout(loadCart(20), 100).then((cart) => console.log(`Total: $${cart.total}`));withTimeout(loadCart(300), 100).catch((error) => console.log(error.message));

Line 3 is the only change to the helpers: Promise<never>. Now line 10 is fine, because T | never is just T. Lines 13 to 17 are a stand-in that answers after delay milliseconds. Line 19 asks for a cart that takes 20 milliseconds, well inside the limit, and prints Total: $12. Line 20 asks for one that takes 300, so the timeout rejects first, and it prints Timed out.

Two cards, Promise<Cart> labelled the task, and Promise<never> labelled the timeout, that only rejects. Arrows from both lead into a shaded card Promise.race, and an arrow from it leads to a card Promise<Cart>, labelled Cart | never is just Cart.Promise<Cart>the taskPromise<never>the timeout: only rejectsPromise.racePromise<Cart>Cart | never is just Cart
Promise.race gives back a promise of either result: Cart | never. A never adds nothing to a union, so the result is Promise<Cart>, and withTimeout can promise a T.

The last helper tries again when a request fails. It works for any request, so it's generic too:

A retry helper that keeps the result's typePop out in the code editor (opens in a new tab)TypeScript
type Cart = { items: number; total: number }; async function retry<T>(task: () => Promise<T>, times: number): Promise<T> {  for (let attempt = 1; ; attempt++) {    try {      return await task();    } catch (error) {      if (attempt >= times) throw error;      console.log(`Attempt ${attempt} failed`);    }  }} let calls = 0;async function loadCart(): Promise<Cart> {  calls++;  if (calls < 3) throw new Error("Server busy");  return { items: 3, total: 12 };} retry(loadCart, 3).then((cart) => console.log(`Total: $${cart.total}`));//                       ^?

Line 3 takes task, a function that gives back a Promise<T>, and gives back a Promise<T> too. Line 4 loops, counting attempts. Line 6 runs the task and waits for it. If it fails, line 8 throws the error once there are no attempts left, and line 9 prints a note before trying again. Lines 14 to 19 are a stand-in that fails twice, then works. Line 21 passes loadCart, so T is Cart, and the answer on line 22 shows the callback gets a Cart. It prints Attempt 1 failed, Attempt 2 failed, then Total: $12.

The await on line 6 matters. Without it, line 6 returns the promise before it fails, the catch on line 7 never sees the failure, and nothing is retried. TypeScript can't catch that: both versions have the right types. Exercise 6 lets you see it.

Loading a checkout page

A real checkout page puts these together: it loads the user and the cart at once, gives up after 100 milliseconds, and shows a message either way:

Load a checkout page with a time limitPop out in the code editor (opens in a new tab)TypeScript
type User = { name: string };type Cart = { items: number; total: number }; function timeout(ms: number): Promise<never> {  return new Promise((_, reject) => {    setTimeout(() => reject(new Error("Timed out")), ms);  });} function withTimeout<T>(task: Promise<T>, ms: number): Promise<T> {  return Promise.race([task, timeout(ms)]);} async function loadUser(): Promise<User> {  return { name: "Asha" };} async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function loadCheckout(): Promise<string> {  const both = Promise.all([loadUser(), loadCart()]);  const [user, cart] = await withTimeout(both, 100);  //     ^?  return `${user.name}, your ${cart.items} items cost $${cart.total}`;} loadCheckout()  .then((message) => console.log(message))  .catch(() => console.log("Checkout is slow. Try again."));

Lines 4 to 12 are the timeout helpers from before, and lines 14 to 20 are the stand-ins. Line 22 says loadCheckout gives back a promise of a message. Line 23 starts both requests, which makes a Promise<[User, Cart]>. Line 24 passes it to withTimeout, so T is the tuple [User, Cart], and the answer on line 25 shows user is still a User after the timeout. Line 26 builds the message, and line 30 prints Asha, your 3 items cost $12. If either request took too long, line 31 would print Checkout is slow. Try again.

Now sort some one-line snippets by what TypeScript says about them:

Error or fine?
  • const total: number = await Promise.resolve(12);
  • const total: number = Promise.resolve(12);
  • async function loadTotal(): number { return 12; }
  • const later: Promise<number> = new Promise((resolve) => resolve(3));
  • const later = new Promise((resolve) => resolve(3)); later.then((n) => n.toFixed(2));
  • if (Promise.resolve(true)) console.log("Ready");
  • Promise.reject(new Error("Down")).catch((error) => console.log(error.code));
  • const [count, label] = await Promise.all([Promise.resolve(3), Promise.resolve("items")]); count.toFixed(2);
Try it yourself
0 of 8 correct

Sort each line by what TypeScript says about it. Each line is checked on its own, as a module, so await works at the top level.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

An async function gives back a promise even when it never waits for anything. This one returns 3 at once, and the mistake is on purpose:

An async function with nothing to wait for (an error on purpose)Pop out in the code editor (opens in a new tab)TypeScript
async function countItems() {  return 3;} const count: number = countItems();

Line 2 returns 3 straight away. Still, line 5 gets Type 'Promise<number>' is not assignable to type 'number': the word async on line 1 is what makes the promise.

  • "An async function gives back what it returns." No. It always gives back a promise of what it returns. Use await to get the value.
  • "TypeScript always tells me when I forget await." No. It tells you when you use the promise as a value. A call on its own line needs a linter rule like no-floating-promises.
  • "Promise.all always keeps each result's type." No. Only for a list written in the call, a list with as const, or a tuple type. A saved plain list mixes them.
  • "Promise<Cart> also says what the error will be." No. It only types the value. What a rejected promise holds is any in .catch and in reason. The Typing errors lesson shows how to handle it.
  • "Writing Promise<Cart> makes the server send a cart." No. Types are gone when the code runs. Check data from a server first, as in Typing fetch & JSON.
  • "setTimeout gives back a number everywhere." No. In the browser it does. Node.js's types say NodeJS.Timeout. ReturnType<typeof setTimeout> fits both.

Practice exercises

Each exercise is TypeScript. Press Edit & run on the starter code to try a fix: the errors update as you type, and the console shows what it prints. Try a guess first, then use the hints.

Exercise 1 · Warm-upRead the type of an async call

What type does order get? Guess first. Then point at order to check, or press Edit & run and add a // ^? line under it.

Starter codePop out in the code editor (opens in a new tab)TypeScript
type Order = { id: number; total: number }; async function loadOrder(): Promise<Order> {  return { id: 7, total: 12 };} const order = loadOrder();

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upFind the line TypeScript marks

    This button never says "Sold out". Which line will TypeScript mark?

    Starter codePop out in the code editor (opens in a new tab)TypeScript
    async function isInStock(): Promise<boolean> {
      return false;
    }
    
    async function showButton() {
      const inStock = isInStock();
      if (inStock) console.log("Add to cart");
      else console.log("Sold out");
    }
    
    showButton();

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeFix the tax

      The code prints Tax: $NaN, and TypeScript marks line 9. Fix it with one word. What does your fixed code print?

      Starter codePop out in the code editor (opens in a new tab)TypeScript
      type Cart = { items: number; total: number }; async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function showTax() {  const cart = loadCart();  const tax = cart.total * 0.25;  console.log(`Tax: $${tax}`);} showTax();

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeKeep each result's type

        The code runs, but line 15 has two errors. Fix line 13 without changing line 15. What does your fixed code print?

        Starter codePop out in the code editor (opens in a new tab)TypeScript
        type User = { name: string };type Cart = { items: number; total: number }; async function loadUser(): Promise<User> {  return { name: "Asha" };} async function loadCart(): Promise<Cart> {  return { items: 3, total: 12 };} async function greet() {  const requests = [loadUser(), loadCart()];  const [user, cart] = await Promise.all(requests);  console.log(`Hi ${user.name}, you have ${cart.items} items`);} greet();

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeMake the timeout fit any promise

          TypeScript marks line 10. Fix the type without changing line 10. What does the fixed code print?

          Starter codePop out in the code editor (opens in a new tab)TypeScript
          type Cart = { items: number; total: number }; function timeout(ms: number) {  return new Promise((_, reject) => {    setTimeout(() => reject(new Error("Timed out")), ms);  });} function withTimeout<T>(task: Promise<T>, ms: number): Promise<T> {  return Promise.race([task, timeout(ms)]);} function loadCart(): Promise<Cart> {  return new Promise((resolve) => {    setTimeout(() => resolve({ items: 3, total: 12 }), 300);  });} withTimeout(loadCart(), 100)  .then((cart) => console.log(`Total: $${cart.total}`))  .catch((error) => console.log(error.message));

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeWhy doesn't it retry?

            TypeScript has no errors here, but the code gives up at once. Find the missing word. What does the fixed code print last?

            Starter codePop out in the code editor (opens in a new tab)TypeScript
            type Cart = { items: number; total: number }; async function retry<T>(task: () => Promise<T>, times: number): Promise<T> {  for (let attempt = 1; ; attempt++) {    try {      return task();    } catch (error) {      if (attempt >= times) throw error;      console.log(`Attempt ${attempt} failed`);    }  }} let calls = 0;async function loadCart(): Promise<Cart> {  calls++;  if (calls < 3) throw new Error("Server busy");  return { items: 3, total: 12 };} retry(loadCart, 3)  .then((cart) => console.log(`Total: $${cart.total}`))  .catch((error) => console.log(`Failed: ${error.message}`));

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              For each question, ask: is this the promise, or the value inside it? The quiz shows its code without the checker, so the error list doesn't give the answers away.

              The Promises & async functions quiz · 7 questionsScore: first tries count
              1. Question 1 of 7What type does cart get on line 7?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type Cart = { items: number; total: number };
                
                async function loadCart(): Promise<Cart> {
                  return { items: 3, total: 12 };
                }
                
                const cart = loadCart();

                Choose an answer to see the explanation.

              2. Question 2 of 7An async function says return 3; and has no return type. What does TypeScript say it gives back?

                Choose an answer to see the explanation.

              3. Question 3 of 7What does TypeScript say about async function countItems(): number { return 3; }?

                Choose an answer to see the explanation.

              4. Question 4 of 7What type does results get on line 14?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                type User = { name: string };
                type Cart = { items: number; total: number };
                
                async function loadUser(): Promise<User> {
                  return { name: "Asha" };
                }
                
                async function loadCart(): Promise<Cart> {
                  return { items: 3, total: 12 };
                }
                
                async function loadBoth() {
                  const requests = [loadUser(), loadCart()];
                  const results = await Promise.all(requests);
                  return results;
                }

                Choose an answer to see the explanation.

              5. Question 5 of 7What happens with the if on line 5?

                Read the code, then predictPop out in the code editor (opens in a new tab)TypeScript
                async function isLoggedIn(): Promise<boolean> {
                  return false;
                }
                
                if (isLoggedIn()) console.log("Welcome back");
                else console.log("Please log in");

                Choose an answer to see the explanation.

              6. Question 6 of 7Why does timeout(ms) give back Promise<never> in this lesson?

                Choose an answer to see the explanation.

              7. Question 7 of 7What type does new Promise((resolve) => resolve(3)) get when nothing else says what it holds?

                Choose an answer to see the explanation.

              Key takeaways

              • Promise<Cart> is a promise that a cart will arrive. Only await or then gets the Cart out.
              • An async function always gives back a promise. Write its return type as Promise<T>.
              • TypeScript catches a forgotten await when you use the promise as a value, including in a condition.
              • Promise.all gives a tuple, like [User, Cart], when it can see each place in the list.
              • Type a timeout as Promise<never> and helpers like withTimeout and retry as generics, so the result keeps its type.

              Remember the one-liner.
              A Promise<T> is not a T: await it to get the value, and let the T flow through your helpers.

              Coming next: Typing errors. It covers the other side of a promise: why a catch block gives you unknown while a .catch callback gets any, and how to type what goes wrong.

              CompleteFrontend Clear concepts. Working examples.