Designing Your Types 11 exercises
explainer

Handle Errors with a Generic Result Type

Consider this Result type:


type Result<TResult, TError> =
| {
success: true;
data: TResult;
}
| {
success: false;
error: TError;
};

The Result type has two type parameters for TResult and TError.

It returns a discriminated union, with one br

Loading explainer

Transcript

00:00 One really nice application of generic types is this kind of result type up here. We have a type result is tResult and tError, so there's two type parameters on result. And we're returning a discriminated union here, where in one branch of the discriminated union, success is going to be true and we return the data.

00:18 Otherwise, we return success false and we return an error there. And so we have a createRandomNumber function. And inside this createRandomNumber function, we have a num is math.random. This could be anywhere between 0 and 1.

00:33 And then we end up with if the number is greater than 0.5, you return success true and data 1, 2, 3. Otherwise, you return success false and error, new error, something went wrong. Now, the cool thing about this is that we're returning a result passing in a number as the data and an error as the error.

00:51 And so when we get this result back, we can actually check if result.success, we end up with result.data and it's typed as number. If I were to make a change here and say result.string, then it's going to yell at me if I don't return the right thing in the right branch because type number is not assignable to type string.

01:09 So this pattern is actually really nice. And if you've ever used Rust before, you might notice that this is how Rust handles errors. And this is just lovely because it means that you don't have to do any try-catching. And try-catching, I mean, you might need some try-catching,

01:25 but by default as a way to check for errors, try-catching can be really annoying because first of all, the error that you get back is not safe as we've seen in previous exercises. And it means that you can just end up with really complicated logic where you're catching errors at certain boundaries and you're not quite sure where they're coming from.

01:43 With this, it's pretty explicit and it means that you can just say, is my result a success? If so, proceed. If not, handle that error. Really, really nice for certain situations when you have really complicated imperative code flow.