Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doesn't work if error is {} or unknown #23

Open
arthurfiorette opened this issue Aug 16, 2024 · 6 comments
Open

doesn't work if error is {} or unknown #23

arthurfiorette opened this issue Aug 16, 2024 · 6 comments
Assignees

Comments

@arthurfiorette
Copy link
Owner

About the tuple-it library. It works if error is of type Error or something like that.
The library doesn't work if error is {} or unknown:

import 'tuple-it/register'

type WorkData = { id: string; title: string; }

async function work(promise: Promise<WorkData>) {
  const [error, data] = await promise.tuple<unknown>()
  // const [error, data] = await promise.tuple<{}>()

  if (error) {
    console.log('Operation failed!')
    return false
  }

  /**
   * Type 'WorkData | undefined' is not assignable to type 'WorkData'.
   * Type 'undefined' is not assignable to type 'WorkData'.ts(2322)
   */
  const value: WorkData = data;

  console.log('Operation succeeded!')
  return true
}

Typescript in strict mode treats exceptions as unknown by default, so semantically to use something else doesn't seem to be correct.

Originally posted by @DScheglov in arthurfiorette/proposal-safe-assignment-operator#3 (comment)

@DScheglov
Copy link

I guess you cannot resolve the issue on the library level.
The problem is not in the union type, the problem is in how narrowing works with if operator:

if (error) ...

is the same as

if (!!error) ...

So, in the then-branch the type of error is infered like Exclude<typeof error, null | undefined | '' | 0 | false | []>
But TS cannot infer the type in the else-branch of the if, so it cannot descriminate the union of tuples.

So, the solution in TypeScript for case {} is looks very simple and you need just update your README file:

- if(error) ...
+ if(error != null)

However, unknown blocks TS from the descriminating the union because null | undefined (error type is narrowed to in the else-branch of if) matches the unknown type as well, so both options are satisified.

The difference between unknown and {} is exactly in null | undefined, however it is very bad idea to recommend using {} instead of unknown, if null or undefined are thrown it can cause undefined and unexpected behavior on the non-error branch -- so, it could be danger (actually, presume that only Error instances could be thrown is also dangerous)

In general, we need the Result type like Rust has and we need the Pattern Matching to be able to work with errors correctly.

@arthurfiorette
Copy link
Owner Author

This is solved in these lines:

tuple-it/index.js

Lines 9 to 13 in 32dc861

if (error instanceof Error) {
return [error]
}
return [new TupleItError(error)]

then i just need to force E being an instanceof Error at:

export declare function tuple<T, E = Error>(

@DScheglov
Copy link

DScheglov commented Aug 16, 2024

@arthurfiorette

oh, it just means that your library doesn't support non-Error exceptions )
So, the fix in your case is really possible:

export type TupleResult<T, E extends Error = Error> = [E] | [null, T]

and so on.
It makes impossible to write promise.tuple<{}>() ...

@arthurfiorette
Copy link
Owner Author

arthurfiorette commented Aug 16, 2024

oh, it just means that your library doesn't support non-Error exceptions )

As it should right? No one expects non-Error exceptions either way, so in case any of them is thrown we have a good wrapper to catch it.

And for the people who use throw as control flow for non Error instances, i don't like you 👍

@DScheglov
Copy link

DScheglov commented Aug 16, 2024

And for the people who use throw as control flor for non Error instances, i don't like you 👍

Say that to vercel with their nextjs

UPD: I've checked out. I was wrong. nextjs and react throw instances of Error class augmented with their internal data. But these exceptions are not "real exceptions", they are used to control flow )

As instance React throws error with the following message:

Error: Suspense Exception: This is not a real error! It's an implementation detail of use to interrupt the current render. You must either rethrow it immediately, or move the use call outside of the try/catch block. Capturing without rethrowing will lead to unexpected behavior.

@thejustinwalsh
Copy link

thejustinwalsh commented Sep 6, 2024

RE: React & Next, would it be possible to provide some configuration to the matcher for TupleItError so that you can rethrow if the error matches based on a callbackFn predicate you provide?

For example, Next.js extends Error and adds a digest property set to specific values...
https://github.com/vercel/next.js/blob/2fa16336ebbbd611309527b02f7aa5e1ceb03106/packages/next/src/shared/lib/lazy-dynamic/bailout-to-csr.ts#L2-L11

For react, you may have to match a pattern against the string in Error.message property, which is not great.

If some error middleware was provided that knows to rethrow specific magic errors, you should be able to configure it for your particular environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants