Assertions: as and !
At customs the sender writes the parcel's declaration. It can also be forged: you write "books" on a box that holds anything at all. The inspector compares papers, not contents, so he will believe it.
In TypeScript that forgery is the as form. It tells tsc: "treat this value as having this type". tsc will not check the value:
const price: unknown = "100";
const amount = price as number;
console.log(amount.toFixed(2)); // ✗TypeError: amount.toFixed is not a functiontsc is silent: after as number it treats amount as a number. But the variable still holds the string "100", a string has no method toFixed, and the program crashes at run time.
The type assertion
The form value as Type is a type assertion. It changes only what tsc believes and does nothing at run time:
- the value does not turn into another type: a string stays a string;
- there is no check at run time: in the JavaScript the line
const amount = price as number;becomesconst amount = price;; tscfinds no error, and the mistake surfaces at run time, often far from theas.
This is not a conversion like Number(price): as converts nothing. Write it only when you are sure you know more about the value than tsc does. Data from outside needs a real check instead of as: typeof or a type predicate.
The as const form is called an assertion too: a const assertion in tsc's messages. But nothing can be forged with it: it tells tsc nothing new about the value, it only asks it not to widen the literal's types.
When as is rejected
as does not go entirely unchecked. If two types have nothing in common, tsc refuses. For example, it rejects the line const n = "42" as number;:
error TS2352: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.The message suggests a way around: assert the type unknown first. The form "42" as unknown as number compiles, but it is a forgery on top of a forgery: n still holds a string. From unknown and any an assertion lets any type through, which is why the lesson's first example passed the check.
The ! operator
The sign ! after an expression is a special kind of assertion: "this is not null and not undefined". It removes them from the type and checks nothing at run time either:
const prices = new Map<string, number>();
prices.set("tea", 120);
const tea = prices.get("tea")!;
console.log(tea.toFixed(2)); // 120.00Let's go through it line by line:
prices.get("tea")returnsnumber | undefined: the key may be missing.!removesundefined, andteahas the typenumber.- The key
"tea"is inprices, so the program prints120.00.
Without ! the line tea.toFixed(2) would not pass the check: tsc would report TS18048: 'tea' is possibly 'undefined'.
satisfies checks and keeps the type
as has an honest neighbor, satisfies. It compares the value with a type but leaves the value's own type as it was:
type Config = { port: number | string };
const config = { port: 8080 } satisfies Config;
console.log(config.port.toFixed(0)); // 8080tsc checked that { port: 8080 } fits Config: the form { port: true } satisfies Config would give an error. Yet config.port kept its inferred type number, so toFixed passes. Let's compare the three forms:
- the annotation
const config: Configchecks the value and changes the type toConfig, and thenconfig.porthas the typestring | number; satisfies Configchecks the value and keeps the inferred type;as Configchecks almost nothing and changes the type.
Interviewers like to ask what as does at run time and how it differs from satisfies. The answer: at run time as does nothing, since it is erased along with the types, while satisfies checks the value against a type without changing its inferred type.
Here the forgery analogy falls short in one respect. Forging declarations is against the law, but as is a legitimate tool. You use it when you know more about a value than tsc does and are ready to answer for it.
Common mistake
! is put where there is no value:
const prices = new Map<string, number>();
prices.set("tea", 120);
const coffee = prices.get("coffee")!; // ✗
console.log(coffee.toFixed(2));TypeError: Cannot read properties of undefined (reading 'toFixed')There is no key "coffee" in prices, get returned undefined, and ! convinced tsc of the opposite. tsc is silent, and the program crashes on the next line. Instead of !, check the value: if (coffee !== undefined).
Key points
aschanges only whattscbelieves: at run time it is erased and converts nothing.- Between unrelated types
asgivesTS2352, andas unknown asgets around the check without changing the value. !removesnullandundefinedfrom the type and checks nothing at run time either.satisfieschecks a value against a type and keeps the inferred type.