Intermediate TypeScript, v2

Intermediate TypeScript, v2 Practical Uses of Top Types

Learning Paths:
Topics:
Check out a free preview of the full Intermediate TypeScript, v2 course:
The "Practical Uses of Top Types" Lesson is part of the full, Intermediate TypeScript, v2 course featured in this preview video. Here's what you'd learn in this lesson:

Mike demonstrates how top types can be used to handle errors by using the `unknown` type in catch blocks to prevent assuming the type of the error. Using top types when receiving API responses from APIs that may change or when passing values through code that doesn't need to know about them.

Get Unlimited Access Now

Transcript from the "Practical Uses of Top Types" Lesson

[00:00:00]
>> All right, let's talk about using top types. What are some practical uses of top types? The first example I'm gonna show you is handling errors. So here we've got a function called doSomethingRisky. It flips a coin and if it's heads, it returns the string ok. And then it flips another coin and it says, all right, if that one's heads, we throw an error.

[00:00:29] And then finally we say, all right, we're gonna throw a string at the end, which is bad. Don't throw strings. How many people in here throw strings? My guy, you shouldn't do this. And I've told you this, so you're not gonna do it now. But how much do you trust everything inside your node modules folder?

[00:00:56] How much do you trust every piece of code that's part of your app? That it's throwing a proper throwable, a proper error. It's almost certain something in there is throwing a string or throwing an object. It's not an error. So, what we're doing here, and this is one place where unknown can be used.

[00:01:17] We're saying we're gonna have a try catch, but we're gonna type this error in the catch block as an unknown. And effectively what we're doing here is we're saying, TypeScript, keep me honest. I am not allowed to assume that this is an error before checking it. If we didn't do this, I forgot what my compiler setting currently is but let's see.

[00:01:43] If we didn't do this, it would be in any. It would be super easy to reach into this e, and do like, Could do e.stack, we could do e.name or e.message. You could treat this, you could be thinking in your mind like, this is probably an error, I will treat it as an error.

[00:02:01] So it's useful to say anything I catch, I wanna type as unknown. And in fact, there is a compiler setting, and I'm just real quick, gonna go to the TS config for this notes package. So this is in packages notes intermediate, ts-v2 and the tsconfig there, and we can say, UseUnknownInCatchVariables, we'll set that to true.

[00:02:33] What this lets us do is we don't even have to say this anymore. E will always be an unknown. I love enabling this flag, it's great. This gives you some assurance and some encouragement to handle throwables robustly, which is important. If you've ever seen a log file with object in it or something.

[00:03:03] Console dot logs object, you'll understand how this could be valuable and how you could handle errors in a special way and strings in a special way. However you need to do this. You could probably even write a reusable function in here that would be, like serialize error or something like that, that could handle all of those different possibilities for you.

[00:03:23] So protip, turn this on and then you don't have to worry about like did we remember to put colon unknown in every single cache block. It's just like the base assumption TypeScript will make is to do this, so that is a great use of unknown. Sorry, there's another good use for unknown.

[00:03:45] I'll just talk about it cuz they don't have it here, but we'll look at it a little bit later in a later chapter when we have an example that involves it, receiving API responses. So sometimes we have control over the API and then the API client. Let's say we're building a browser-based app with Typescript.

[00:04:06] Sometimes we know what the API response is gonna be or it's like guaranteed to be a set of a couple things and we could hard code some assumptions about the types. You'd say, here's what a successful API shape looks like, here's what an error looks like, could be one of these two things.

[00:04:23] But if you're talking to APIs that you don't control, in particular where they could change underneath you, and you don't have control over the timing of that change, or whatever that is. Some people like to say, okay, I want to treat this as an unknown when my fetch returns its JSON response.

[00:04:44] And then I'm going to apply some typeguards to make sure that this is what I expect it to be. And the end result there, there are pros and cons, but the cons are you're paying a cost at runtime to apply these typeguards and to validate that this thing is what you hope it's gonna be.

[00:05:04] But what you get there is you get a much more actionable error when it's thrown. It's not gonna be sort of the API response flows through, maybe it goes into a bunch of React components, and you're like, why is this property not here? It should be here and you trace it back and ultimately like, the API changed underneath me.

[00:05:22] Instead, you'll sort of be validating that API response as it enters your app, and you could see, all right, expected shape was this, and it came in, and it was not that shape throw. So that's another good use of unknown. Also, one more use, if you're passing a value through a piece of code that doesn't need to know about it.

[00:05:45] Sometimes you can treat it as an unknown, and then say you have a library that takes something a consumer of the library provides you, and you just wanna hand it back to them unaltered. Unknown would prevent you from sort of touching that object, or becoming entangled with any specific shape that that object could be.

[00:06:04] It's just like some value that's like piggybacking in your library and eventually goes back to the person that provided it with you. So we would describe this as like an opaque value. Sometimes there's a good reason for that. Sometimes you wanna make sure that in your code you're treating it as opaque.

[00:06:23] That's sort of establishing a guarantee that you're not going to be making any assumptions about this thing, you can't, not unless you start applying type parts to it. And even if you do that, those assumptions are validated in your code. You only treat it as a string if it's actually a string.

[00:06:43] Let's see, Marjon asks, should we to reuse typecasting then or setting up some typeguards? If I control both my API and my API client, I would probably cast. I mean, if you wanna look at the my full stack TypeScript course on Frontend Masters, that's an example of where we use a single set of types that are shared both in the backend and the frontend.

[00:07:09] And there, it's very clear. Technically, we're casting, but we have this very strong relationship. It's literally a server and a client folder in the same git repo. Everything is in sync with every single git commit. In that situation, I'd say definitely cast, if you still control a backend and the frontend, but they're not sharing a single set of types.

[00:07:35] I might cast there as well, because often, there's a coordinated release, where you make sure that get the API out there first, and it's always backwards compatible. And then we roll out the UI, which depends on the new API. It's sort of a release management problem there. Where I would definitely use typeguards is if you're talking to another API, maybe one that is not a SAS product, it would be an API that sort of, Maybe it's very few people use it and there's a high rate of change, or you don't have any way of knowing when something changes, that's where I would say you should use typeguards.

[00:08:21] If you were scraping an HTML page, for example, and you represent that as JSON, HTML is gonna change all the time. People don't think of that as an API. So that's where I would say use typeguards. That will change, and it will bust your app in terrifying and mysterious ways.

[00:08:39] It's better to have it fail in a very clear way, and then you know exactly how to fix it. You can get that out fast and move on.