neverthrow icon indicating copy to clipboard operation
neverthrow copied to clipboard

[Question] The need for second parameter passed to `fromPromise`

Open jansedlon opened this issue 1 year ago • 4 comments

Hello,

Sorry for creating an issue but there are no discussions.

My question is why is passing errorFn mandatory? In 99% of cases i just type e => e because there's no need to create my own error. What are your use cases for creating custom errors? When im doing a function call to a database, passing e => new Error("Creating something failed", {cause: e} ) seems redundant.

Any explanation is greatly appreciated.

jansedlon avatar Sep 25 '24 13:09 jansedlon

Hello @jansedlon

fromPromise acts as an interface between value-typed world Promise<V> to the value- and error-typed world ResultAsync<V,E>. It is there that you should provide the error types ‘E.

if you use ‘e => e’ as an errorFn you get a ‘ResultAsync<V, any>’ which kinda wastes neverthrow’s purpose.

sorry for the typos, mobile

paduc avatar Sep 25 '24 15:09 paduc

Hello @paduc , thank you for your explanation. Am I correct that in 99% of cases what is thrown is an instance of Error? So why not change the default error type to Error and make errorFn optional?

Do you have any examples of yours? For example when talking to a DB, etc.

I know I could just make a wrapper myself but I'm just wondering about this.

jansedlon avatar Sep 25 '24 16:09 jansedlon

@jansedlon I don't think 99% of cases are of type Error quite the contrary.

Here's an example taken from the prisma documentation:

import { PrismaClient, Prisma } from '@prisma/client'

const client = new PrismaClient()

try {
  await client.user.create({ data: { email: '[email protected]' } })
} catch (e) {
  if (e instanceof Prisma.PrismaClientKnownRequestError) {
    // The .code property can be accessed in a type-safe manner
    if (e.code === 'P2002') {
      console.log(
        'There is a unique constraint violation, a new user cannot be created with this email'
      )
    }
  }
  throw e
}

Here is what you would do with neverthrow

import { PrismaClient, Prisma } from '@prisma/client'

class UserAlreadyExistsError extends Error {}
class DatabaseError extends Error {}

const client = new PrismaClient()

const userCreationPromise = client.user.create({ data: { email: '[email protected]' } })

const userCreationResult = Result.fromPromise(userCreationPromise, (e) => {
   if (e instanceof Prisma.PrismaClientKnownRequestError) {
    if (e.code === 'P2002') {
      return new UserAlreadyExistsError()
    }
  }
  return new DatabaseError()
})
// ResultAsync<boolean, UserAlreadyExistsError | DatabaseError>

It helps isolate the different error cases for our own domain. We do not want prisma specific errors which might become irrelevant when we change ORMs.

paduc avatar Sep 26 '24 10:09 paduc

@jansedlon in my own codebase, I have a general UnlikelyError class which is used for a general "this should not happen, but I'm not 100% sure an error can't occur here.". I was inspired by the UNLIKELY error code used as a catch-all in interfaces such as Bluetooth.

I also have a utility shouldSucceed<T>(promise: Promise<T>): ResultAsync<T, UnlikelyError>. It simply calls fromPromise with a second parameter that just constructs an UnlikelyError. Basically, this insulates my codebase from any async third-party stuff that I don't know whether or what types it can throw. Then from error reports we can discover if there are errors affecting production, and if so we can add that specific case as a different error type that needs handling

macksal avatar Oct 01 '24 15:10 macksal