Promise<T> and async
What you'll learn:
- read the type
Promise<T>and explain why anasyncfunction always returns a promise; - get a value of type
Twithawaitand recognize a forgottenawaitfrom atscmessage; - type
Promise.alland the error incatch, whose type isunknown; - check data from outside with a type predicate and tell that check apart from
as.

At a café you place an order and get a receipt with a number in return. The dish does not exist yet: the kitchen is cooking it. The receipt is a promise: when the dish is ready, you get it for this receipt. You cannot eat the receipt; you can only exchange it for the dish at the pickup window.
In JavaScript that receipt is a promise. A function that takes a while to get data, for example from a server, returns a promise at once, and the value itself arrives later. TypeScript writes on the receipt which dish it is for: Promise<User> is a receipt for a user.
What happens if you mix up the receipt and the dish? The function loadUser returns a promise, and the code reads a name from the result right away:
type User = { name: string };
async function loadUser(): Promise<User> {
return { name: "Anna" };
}
async function main() {
const user = loadUser();
console.log(user.name); // ✗
}
main();error TS2339: Property 'name' does not exist on type 'Promise<User>'.The message says that the type Promise<User> has no property name. user holds the receipt, not the user. Without type checking the program would not crash: it would quietly print undefined.
The type Promise<T>
As you already know from the generics theme, Promise<T> is a generic type. The angle brackets hold the type of the value the promise gives back when it finishes:
Promise<number>gives back a number;Promise<User>gives back an object of typeUser;Promise<void>gives back nothing useful and only reports that the work is done.
The promise itself and the value inside it are different things. A promise has the methods then and catch, but no user properties. That is why tsc refused to read user.name.
An async function returns a promise
The word async in front of a function changes its result type. Whatever stands in return, the calling code gets a promise holding that value:
- a function with
return 41returnsPromise<number>; - a function with no
returnreturnsPromise<void>.
This holds even when the value is ready at once. The café still hands you a receipt, though the dish is already on the counter.
That is why the result type of an async function is written as a promise. If you write the type of the value itself, tsc stops you:
async function loadCount(): number { // ✗
return 41;
}error TS1064: The return type of an async function or method must be the global Promise<T> type. Did you mean to write 'Promise<number>'?At the end of the message tsc suggests the right form: Promise<number>. You may also leave the result type out: TypeScript infers Promise<number> from return 41 on its own.
await unwraps the promise
To get the dish, you exchange the receipt at the pickup window. In code that is what await does: it waits until the promise finishes and gives back the value. For TypeScript this means that await turns Promise<T> back into T:
type User = { name: string };
async function loadUser(): Promise<User> {
return { name: "Anna" };
}
async function main() {
const user = await loadUser();
console.log(user.name); // Anna
}
main();Let's walk through the example line by line:
loadUseris declared with the result typePromise<User>.await loadUser()waits for the promise, souserhas the typeUser, notPromise<User>.user.namepasses the check, and the program printsAnna.awaitsits inside theasyncfunctionmain, andmain()is called at the end of the file: that way the example works in any file.
The widget shows the same value with await and without it. Watch the tooltip: without await the value has a promise type, with await it is number, and tsc does not let a forgotten await through in arithmetic.
1async function loadCount() {editor hoverloadCount: () => Promise<number>2 return 41;3}4 5async function main() {6 const pending = loadCount();7 const count = await pending;8 console.log(count + 1);9 console.log(loadCount() + 1);10}11 12main();
The body returns 41, but async wraps the result in Promise<number>.
The TS2365 message from the fifth step is worth remembering: the operator + cannot be applied to Promise<number> and 1. It says '1', not 'number': TypeScript names a literal by its exact type. If an error message shows Promise<…> where you expected a value, an await is almost always missing.
Here the receipt analogy stops working. At the pickup window a real receipt is taken from you, but the promise stays with you. If you write await for the same promise a second time, it gives back the same value at once.
Interviewers like to ask which type an async function returns. The answer: always Promise<T>, where T is the type of what stands in return, and await gives a value of type T.
Common mistake
The await is forgotten in a condition:
async function isAdmin(): Promise<boolean> {
return false;
}
async function main() {
if (isAdmin()) { // ✗
console.log("admin panel");
}
}
main();error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined.The message says that the condition is always true because the promise always exists. The receipt is an object, and an object in an if always counts as true, whatever you later get for it. Without type checking the program would show the admin panel to everyone, though isAdmin returns false. You need to wait for the answer: if (await isAdmin()).
Key points
Promise<T>is a promise that gives back a value of typeT; the promise itself has none of that value's properties.- An
asyncfunction always returnsPromise<T>, and its result type is written asPromise<number>, notnumber. awaitunwrapsPromise<T>back intoT.Promise<…>in aTS2339,TS2365orTS2801message where a value was expected almost always means a forgottenawait.