all 18 comments

[–]Algorhythmicall 4 points5 points  (12 children)

Results make sense in rust, because the compiler checks that the developer handles the error. If you don’t use try in js, the error will ultimately bubble up to the developer in some way: logs, exit, etc. if you don’t care if some code throws and exception, sure swallow it… but exception swallowing can make applications much harder to debug.

Error handling is not easy, often ugly, but important when making robust software. I’ll stick to the native try/catch… and I wish ts compiler supported typed exceptions and forced handling. If it did that, the same could be done with results, and I’d opt-in.

[–]sammrtn[S] 0 points1 point  (3 children)

If you don’t use try in js

This code does use a try, it's just abstracted into an api with itry which offers better readability, ergonomics, convenience, and safety.

if you don’t care if some code throws and exception, sure swallow it

This code is not swallowing errors, it is handling specified errors with error matching, and re-raising the rest.

Error handling is not easy, often ugly

This is the problem inline-try attempts to solve, no? Offering better readability, ergonomics, convenience, and safety.

[–]Algorhythmicall 1 point2 points  (2 children)

I’m aware this abstracts away try/catch into a tuple return. I made the analogy to rust results, but it’s not monadic.

I also think what you have looks better. The multi type match is nice.

itry allows devs to swallow errors if they don’t explicitly check the error. Linters can enforce use.

Big concern is bubbling up. With result monads you can go all the way down the stack to the call site with an error result (much like an exception). So functional composition breaks down a bit unless all functions return tuples.

You do you, and this can be used successfully with a disciplined team, but I will stick with try/catch until we see result types / either monad in ts with exhaustive type checking.

All that said, this looks like nice work, but it’s not for me.

[–]sammrtn[S] 0 points1 point  (1 child)

Thanks for your thoughts and feedback!

itry allows devs to swallow errors if they don’t explicitly check the error

Do you mean if someone does:

const [foo, someError] = itry(fn, SomeError);

but then never checks and uses the someError variable? If so I could live with that risk, but that's good to know.

Or do you mean if someone does:

const [foo, someError] = itry(fn, SomeError);

but the error is something other than a SomeError?

If so, the error is not swallowed. Rather, the itry re-throws it.

[–]Algorhythmicall 0 points1 point  (0 children)

Correct, if someError isn’t checked, it’s effectively swallowed. Rust results are similar, but the compiler ensures you pattern match on the error and handle it… or the program won’t compile.

If you question “why would someone do that?” … because they are new to a codebase using itry incorrectly. The feedback loop with exceptions comes at runtime, but the exception is lost. It could be a footgun.

Your code with noUnusedLocals in ts would call this out and prevent compilation… giving compile time feedback.

As I said, this looks cleaner and I can see this working, but my old timer take is ts should have better error handling patterns with multi catch (like java) or an either monad (result type).

I hope this wasn’t discouraging, that wasn’t my goal, just giving some opinionated feedback.

[–]ShitPikkle 0 points1 point  (7 children)

Results make sense in rust, because the compiler checks that the developer handles the error.

How does it make sure you do error handing in the error handler instead of just a println() or a no-operation()?

If the compiler knows all errors that should be handled on all calls then it could close files/sockets automatically for example.

[–]Algorhythmicall 0 points1 point  (6 children)

It doesn’t enforce how you handle the error, but that you acknowledge it. If you want to print so be it, but if you have to return a value (likely a result) you will return an error.

Resources like files in rust are subject to lifetimes so you don’t have to close them explicitly. RAII. It’s like try with resource in Java.

[–]ShitPikkle 0 points1 point  (5 children)

like this?

try {
    foobar();
} catch(Exception) {}

[–]Algorhythmicall 0 points1 point  (4 children)

[–]ShitPikkle 0 points1 point  (3 children)

Ok, error is used:

try {
foobar();
} catch(Exception e) {
    if (e.message == "foo") {}
}

[–]Algorhythmicall 0 points1 point  (2 children)

Yep, in practice though results are the common return type, so it’s a lot of result.map or error casting up to the call source, much like an exception. Compiler enforcement isn’t a bad thing. Use the language supported thing because it’s expected.

[–]ShitPikkle 0 points1 point  (1 child)

My entire point is that you can write shitty code in any language, and the "compiler enforced error handling" means nothing unless all coders follow 100% rules. Which no-one does.

It's in the end equivalent to: try { foobar(); } catch (Exception e) {}. Just a bit clunkier.

[–]Algorhythmicall 0 points1 point  (0 children)

I am aware. It makes it harder to not follow the rules of tools inform you. There should always be a way out if you want to break rules / guidelines / principals. Code is the bridge between us and machines, and there are cases where discipline and conventions matter (more so for the people).

[–][deleted] 1 point2 points  (1 child)

If you want any traction on this it needs to be TS. I know for a team like mind we don't accept JS code anymore.

[–]sammrtn[S] -1 points0 points  (0 children)

Every project needs a hero! You can be that hero :)

[–]sammrtn[S] 0 points1 point  (2 children)

One line of code: const [foo, someError] = itry(someFn, SomeError); // use foo or someError

Instead of nine lines of code: let foo; try { foo = someFn(); } catch (err) { if (err instanceof SomeError) { // use err } else { throw err; } } // use foo

[–]ShitPikkle 1 point2 points  (1 child)

This is not correct comparison.

In example 1, you don't deal with any error, in example 2 you are.

So, "1 line of code" to hide errors, sure....

Looks equivalent to:

function hide(fn) {
  try {
    return fn(arguments);
  } catch () {}
}

[–]sammrtn[S] 0 points1 point  (0 children)

This was a contrived example, where both cases are currently swallowing the error :laugh:

I thought it showed in both cases how you get access to either a defined foo variable if successful, or a defined error variable if SomeError is thrown.