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.
- 01Read promise typesTell a Promise<Cart> from a Cart, and see how await, then, and Awaited unwrap one.
- 02Type async functionsKnow that an async function always gives back a promise, and write its return type as Promise<T>.
- 03Combine 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:
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):
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.
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.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 aPromise<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
totalfrom the promise - In real life: When it buzzes, you swap it for the coffee
- In TypeScript:
awaitswaps the promise for theCart
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:
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:
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:
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();Total: $12CartNo errorsawait 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.
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:
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:
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:
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:
| When you write | What 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(): number | An error: an async function always gives back a promise, so its return type must be Promise<…>. |
async function save() { … } with no return | It 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):
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):
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.
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:
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();Asha has 3 itemsreadonly [Promise<User>, Promise<Cart>]No errorsas 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.
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:
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:
| What you call | What you get back | What 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.race | User | Cart: the first one to finish. | It fails if the first one to finish failed. |
Promise.any | User | 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:
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:
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:
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):
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.
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:
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:
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:
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);
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.
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:
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
awaitto 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
anyin.catchand inreason. 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.
What type does order get? Guess first. Then point at order to check, or press Edit & run and add a // ^? line under it.
type Order = { id: number; total: number }; async function loadOrder(): Promise<Order> { return { id: 7, total: 12 };} const order = loadOrder();type Order = { id: number; total: number }; async function loadOrder(): Promise<Order> { return { id: 7, total: 12 };} const order = loadOrder();// ^?loadOrder gives back Promise<Order>, and line 7 has no await, so order is the promise. The query on line 8 shows const order: Promise<Order>.
This button never says "Sold out". Which line will TypeScript mark?
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();async function isInStock(): Promise<boolean> { return false;} async function showButton() { const inStock = await isInStock(); if (inStock) console.log("Add to cart"); else console.log("Sold out");} showButton();Line 7 tests inStock, which is a Promise<boolean>, so TypeScript says the condition will always return true. Adding await on line 6 makes it a boolean, and the code prints Sold out.
The code prints Tax: $NaN, and TypeScript marks line 9. Fix it with one word. What does your fixed code print?
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();type Cart = { items: number; total: number }; async function loadCart(): Promise<Cart> { return { items: 3, total: 12 };} async function showTax() { const cart = await loadCart(); const tax = cart.total * 0.25; console.log(`Tax: $${tax}`);} showTax();With await, cart is a Cart, so cart.total is 12 and the tax is 12 × 0.25 = 3. Without it, the code printed Tax: $NaN, because undefined * 0.25 isn't a number.
The code runs, but line 15 has two errors. Fix line 13 without changing line 15. What does your fixed code print?
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();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()] as const; const [user, cart] = await Promise.all(requests); console.log(`Hi ${user.name}, you have ${cart.items} items`);} greet();as const makes the list a tuple, so Promise.all gives back [User, Cart], and line 15 can read user.name and cart.items. It prints Hi Asha, you have 3 items.
TypeScript marks line 10. Fix the type without changing line 10. What does the fixed code print?
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));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(): 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));With Promise<never>, the race gives Promise<T | never>, which is Promise<T>, so line 10 is fine. The cart takes 300 milliseconds and the limit is 100, so it prints Timed out.
TypeScript has no errors here, but the code gives up at once. Find the missing word. What does the fixed code print last?
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}`));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}`)) .catch((error) => console.log(`Failed: ${error.message}`));With await, the failure happens inside the try, so the catch sees it and tries again. It prints Attempt 1 failed, Attempt 2 failed, and last Total: $12. TypeScript had no errors for either version.
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.
Question 1 of 7What type does
cartget on line 7?Read the code, then predicttype 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.
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.
Question 3 of 7What does TypeScript say about
async function countItems(): number { return 3; }?Choose an answer to see the explanation.
Question 4 of 7What type does
resultsget on line 14?Read the code, then predicttype 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.
Question 5 of 7What happens with the
ifon line 5?Read the code, then predictasync 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.
Question 6 of 7Why does
timeout(ms)give backPromise<never>in this lesson?Choose an answer to see the explanation.
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. Onlyawaitorthengets theCartout.- An
asyncfunction always gives back a promise. Write its return type asPromise<T>. - TypeScript catches a forgotten
awaitwhen you use the promise as a value, including in a condition. Promise.allgives a tuple, like[User, Cart], when it can see each place in the list.- Type a timeout as
Promise<never>and helpers likewithTimeoutandretryas 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.