Do Not Catch Exceptions in TypeScript
The problem with exceptions is that they are not type-safe
Example. We are writing a function
getEmailProvider(email) which has three possible outcomes:
- The email provider is supported
- The email provider is not supported
- Email is not valid
Using return value for the successful outcome 1. and exceptions for the error outcomes 2. and 3. looks like a good idea.
We defined a custom exception for each error outcome by extending the built-in
Error object. This is not straightforward in TypeScript but there is an NPM package ts-custom-error available to help.
The code that is calling
getEmailProvider() wraps it in
try..catch and handles each known exception with
Here comes the problem with exceptions. They are not part of the
getEmailProvider() function signature. Each time we use the function, we have to look into its source code to learn about all the possible exceptions we should handle. This becomes nearly impossible to track if the function is using other functions that can throw. We could use a JSDoc annotation
@throws but it will get out of date — guaranteed.
Now let’s rewrite this code using result type instead.
We wrapped the successful and error outcomes into one type—
EmailProviderResult and we are always returning it.
The code that is calling
getEmailProvider() handles all known outcomes with conditional statements. Our IDE is helping us with suggestions. The compilation fails if we use an invalid error code.
Interestingly although this code is type-safe, it is simpler. There is only one level of nesting while the example with exceptions has two. The variable
result is available in the entire block while with exceptions it is available only inside the
try block. These look like small details but can play a big role in the overall code cleanliness.
Do We Still Need Exceptions?
Yes. Exceptions are a great way to quickly terminate the application at any depth level if there is no way to continue. The built-in
Error exception is good enough for this.
Ideally, there should be one
catch in your application— The handler that renders generic 500 Oops! error page or API response.
Throw an exception when you want to alarm the person that’s an on-call duty for your application. Return result type for everything else.
Be prepared to refactor what once used to be clean code. The exception vs result decision changes over time with the context where your code is used.