Do Not Catch Exceptions in TypeScript

The problem with exceptions is that they are not type-safe

Tomáš Veselý
2 min readApr 6, 2020
Photo by John Ruddock on Unsplash

Example. We are writing a function getEmailProvider(email) which has three possible outcomes:

  1. The email provider is supported
  2. The email provider is not supported
  3. 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 instanceof.

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.

Result Type

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.

If this article convinces you that result types are the right approach, look into some of the existing generic solutionsneverthrow, ts-results or @usefultools/monads to name a few.

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.

Conclusion

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.

--

--