DEV Community

Discussion on: Type-Safe Error Handling In TypeScript

Collapse
 
_gdelgado profile image
Gio

This is not true. I'm using neverthrow in production right now at the company that I work at.

You have to make a distinction between code that you wrote that you know won't throw, and code that others have written that you are not sure will throw or not.

For example, at my company, we use knex for database access. Knex is a huge library and determining in which circumstances it will throw or not is not worth anyones time. Instead, database operations have try/catch arms to catch errors from knex.

By wrapping this kind of code inside of try/catch you guarantee a typesafe interface to the consumers of your functions. Here's an example:

import { ok, err } from 'neverthrow'

const getWidget = async (widgetId: number): Promise<Result<Widget, DatabaseError>> => {
  try {
    const widget = await db('widgets').select('*').where('id', widgetId);

    return ok(widget)
  } catch (e) {
    return err(getDbError(e))
  }
}
Enter fullscreen mode Exit fullscreen mode

Now knex is being used in a typesafe way.

Thread Thread
 
architectophile profile image
architectophile • Edited

Okay. I got your point. So when you need to use a third-party library that might be throwable you wrap it inside of a try/catch statement and turn it into a neverthrow style module.
But what if one of your colleagues added a new feature that uses a new third-party library and forgot to wrap it inside of a try/catch statement?

For example,

import { makeHttpRequest } from './http-api.ts'
import { throwableMethod } from 'third-party-lib'

const run = async () => {
  const result = await makeHttpRequest('https://jsonplaceholder.typicode.com/todos/1')

  result
    .map(responseData => {
      // do something with the success value
      throwableMethod('this is a new feature')
    })
    .mapErr(errorInstance => {
      // do something with the failure value
    })
}

run()
Enter fullscreen mode Exit fullscreen mode

Wouldn't it lead to a more serious problem because inside your core codebase(like in your business rules) you don't have any try/catch statements than when you have some try/catch statements somewhere so the thrown errors will be caught at some point and you can handle it before it causes any serious problems?

Since at the beginning of your article you pointed out that "what if you or a colleague forget to wrap makeHttpRequest inside of a try / catch block.", I think that the same thing could happen even if you stick to use neverthrow style because when you use something new that you are not sure if is throwable or not, you must wrap it inside of a try/catch statement.

If there's something I got wrong, please let me know what I misunderstood.
Actually I admire your idea and work. I'm just curious if it's really resolving the core issues.

Thread Thread
 
_gdelgado profile image
Gio • Edited

But what if one of your colleagues added a new feature that uses a new third-party library and forgot to wrap it inside of a try/catch statement?

Yeah this definitely can occur. However, code review should prevent this sort of situation from happening too often. And it's ok. People make mistakes, no one's perfect. The team can retractively wrap the unsafe code in a try catch as part of their post-mortem.

This module isn't about creating the perfect codebase. Nor am I saying that you should never throw exceptions (how ironic). Strict adherence to dogma is never good!

What I am trying to say is this:

  • using throw as a control flow mechanism is suboptimal
  • modelling the various states of your functions (including whether your function could fail or not) within the type system leads to self-documenting code that is more obvious to others and easier to maintain

Wouldn't it lead to a more serious problem because inside your core codebase(like in your business rules) you don't have any ...

Hmm this depends at what depth the error is coming from and whether something higher up on the call stack does have a try / catch. The team needs to collectively agree to this sort of approach (hello coding guidelines!). This is more of a cultural / non-technical problem around enforcing a certain thing in your codebase.

Also, having a "catch all" try catch statement isn't really going to help you much. If you don't know what you're catching then you can't do much with it. So the counterargument in favor of having one catch statement at the top of your program is kind of irrelevant. All you can do is log the error? And maybe return a 500 to your users?