Skip to content

Commit 419e47b

Browse files
committed
Assertion functions
1 parent b9a0589 commit 419e47b

File tree

1 file changed

+38
-19
lines changed

1 file changed

+38
-19
lines changed

book-content/chapters/16-the-utils-folder.md

Lines changed: 38 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,6 @@ const result: Omit<
300300

301301
Note how clever TypeScript is being here. Even though we didn't specify a return type for `removeId`, TypeScript has inferred that `result` is an object with all the properties of the input object, except `id`.
302302

303-
<!-- CONTINUE -->
304-
305-
### Type Safe vs Type Faith
306-
307-
<!-- TODO -->
308-
309303
## Type Predicates
310304

311305
A type predicate is a special return type that tells TypeScript that a function returns a Boolean value that says something about the type of one of its parameters.
@@ -315,7 +309,7 @@ For example, say we want to ensure that a variable is an `Album` before we try a
315309
We can write an `isAlbum` function that takes in an input, and specifies a return type of `input is Album`. The body of the function will check that the `input` passed in is a non-null object with the required properties of an `Album`:
316310

317311
```typescript
318-
function isAlbum(input): input is Album {
312+
function isAlbum(input: unknown): input is Album {
319313
return (
320314
typeof input === "object" &&
321315
input !== null &&
@@ -361,6 +355,8 @@ let notAnAlbumTitle = getAlbumTitle("Some string");
361355
console.log(notAnAlbumTitle); // "Unknown Album"
362356
```
363357

358+
### Type Predicates Can be Unsafe
359+
364360
Type predicates are a great technique to be aware of, and are particularly useful when working with union types.
365361

366362
However, there are situations where they aren't as type-safe as they may appear.
@@ -377,44 +373,67 @@ In this case, any object passed to `isAlbum` will be considered an `Album`, even
377373

378374
## Assertion Functions
379375

380-
Assertion functions are similar to type predicates, but they use the `asserts` keyword instead of `is`. They are used to assert that a condition is true and to narrow the type of a variable, and will throw an error if the condition is false.
376+
Assertion functions look similar to type predicates, but they're used slightly differently. Instead of returning a boolean to indicate whether a value is of a certain type, assertion functions throw an error if the value isn't of the expected type.
381377

382378
Here's how we could rework the `isAlbum` type predicate to be an `assertIsItem` assertion function:
383379

384380
```typescript
385381
function assertIsAlbum(input: unknown): asserts input is Album {
386-
if (!isAlbum(input)) {
382+
if (
383+
typeof input === "object" &&
384+
input !== null &&
385+
"id" in input &&
386+
"title" in input &&
387+
"artist" in input &&
388+
"year" in input
389+
) {
387390
throw new Error("Not an Album!");
388391
}
389392
}
390393
```
391394

392-
The `assertIsAlbum` function takes in a `input` of type `unknown` and asserts that it is an `Album` using the `asserts input is Album` syntax. If the `isAlbum` function returns `false`, an error is thrown.
395+
The `assertIsAlbum` function takes in a `input` of type `unknown` and asserts that it is an `Album` using the `asserts input is Album` syntax.
393396

394-
Narrowing with assertion functions also works similarly to type predicates, with a notable difference:
397+
This means that the narrowing is more aggressive. Instead of checking within an `if` statement, the function call itself is enough to assert that the `input` is an `Album`.
395398

396399
```typescript
397400
function getAlbumTitle(item: unknown) {
401+
// 'item' is unknown here
402+
398403
assertIsAlbum(item);
399-
// At this point, 'item' is of type 'Album'
404+
405+
// After the assertion, 'item' is narrowed to 'Album'
400406
console.log(item.title);
401407
}
402408
```
403409

404-
There's no need to use a conditional statement when calling `assertIsAlbum` because the function will throw an error if the `item` isn't an `Album`. If the function doesn't throw an error, we can access the `item`'s properties without any type errors.
410+
Assertion functions can be useful when you want to ensure that a value is of a certain type before proceeding with further operations.
411+
412+
### Assertion Functions Can Lie
405413

406-
However, it's important to note that TypeScript won't alert you of the an error until the code is compiled.
414+
Just like type predicates, assertion functions can be misused. If the assertion function doesn't accurately reflect the type being checked, it can lead to runtime errors.
407415

408-
Creating a variable by calling `getAlbumTitle` with a string will not raise an error until the code is compiled:
416+
For example, if the `assertIsAlbum` function doesn't check for all the required properties of an `Album`, it can lead to unexpected behavior:
409417

410418
```typescript
411-
let title = getAlbumTitle("Some string"); // no error in VS Code
419+
function assertIsAlbum(input: unknown): asserts input is Album {
420+
if (typeof input === "object") {
421+
throw new Error("Not an Album!");
422+
}
423+
}
424+
425+
let item = null;
426+
427+
assertIsAlbum(item);
412428

413-
// during compilation, TypeScript will raise an error:
414-
Error: Not an Album!
429+
// 'item' is now narrowed to 'Album', even though it's
430+
// null at runtime
431+
item.title;
415432
```
416433

417-
Assertion functions are particularly useful when you want to validate the type of a variable and throw an error if the validation fails. As with type predicates, it's important to ensure that the assertion function accurately reflects the type being checked in order to avoid any potential runtime issues.
434+
In this case, the `assertIsAlbum` function doesn't check for the required properties of an `Album` - it just checks if `typeof input` is `"object"`. This means we've left ourselves open to a stray `null`. The famous JavaScript quirk where `typeof null === 'object'` will cause a runtime error when we try to access the `title` property.
435+
436+
<!-- CONTINUE -->
418437

419438
## Function Overloads
420439

0 commit comments

Comments
 (0)