Results gently pass errors
Why throw when you can gently hand something over?
Traditional JS/TS error handling
Let’s say we want to make a function that calculates a percentage based on n
, which is the number of points earnes, and total
, which is the maximum number of points, and then formats that percentage into a string for display:
It works! And TypeScript looks pretty happy doesn’t it! Not a single squiggly line in sight!
Job well done, right?
Not so fast. percent()
can be called in unsafe ways without raising any flags:
percent(2, 1)
will return"200%"
percent(-1, 2)
will return"-50%"
percent(0, 0)
will returnInfinity%
!
So now your beautiful succinct function turns into this monstrosity:
And the errors have to be handled like this:
But it’s fine, now your code has no undefined behavior! What a good programmer you are! Want a treat? Want a tasty, juicy TypeError
?
Let me just throw that TypeError
you wanted so much right at your face you fu-
Sorry, sorry, I’ve been traumatized by exceptions. I’m sorry for lashing out at you, it’s not your fault. You didn’t design the language. I’m sorry.
The problem is that you have to expect everyone to handle all the errors your function might throw:
This is really ugly, but if you could assign a type to the error
in the catch block, it would be fine. The compiler would tell you what errors to handle.
But since error
is always typed as any
, and you don’t have every possible throw value memorized, you’ll probably do this instead:
And you’re only going to do that after this throws in production on a Saturday at 2 AM and you’re trying to figure out why this function even threw because there’s no way to specify that it even could throw:
JavaScript tells you:
Types? You’re acting like you haven’t memorized every line of code in your
node_modules
folder. Kinda sus.
TypeScript goes:
What, you don’t know every possible error the libraries you use can throw? I’m already handling all these types and you want me to help with errors too? Can’t you do anything on your own? Pathetic.
I think there’s a nicer way.
Handle with care!
If vanilla error handling is equivalent to the way ${DELIVERY_COMPANY}
(mis)handles packages, handling errors with Result<T, E>
is like going out and manually delivering all your gifts by hand.
Let’s re-implement our percent()
function with Result<T, E>
now:
Running the program will print:
Note that the error case is an array of length 2. This is actually an instance of our PercentError
enum specified as the error type, and we can nest another match()
to properly handle all the PercentError
cases.
This match statement now logs Total is less than points earned! 200%
to the console.
Or don’t!
Of course, while I don’t reccomend it, there is nothing physically stopping you from tossing and throwing your Result
s.
We can update our code like so:
Which would change our output to the following and crash our program:
Or at least, have some insurance.
What if you want the brevity of unwrap()
with the safety of match()
? You can use or()
, which lets you specify a fallback value in case of an error:
Which makes our program return "0%"
instead of crashing: