top | item 38080688

Unwrapit provides a way to handle errors in JS/TS

50 points| musicq | 2 years ago |musicq.gitbook.io

70 comments

order
[+] oweiler|2 years ago|reply
I've come to the conclusion that such a library offers little benefit in languages which embrace exceptions.

You have to basically wrap everything, making the code hard to read, and without proper ADTs and pattern matching it's easy to forget handling all cases.

There's also no way to prevent ppl from falling back to exceptions, so now you have a mess of exception and result handling.

And bc there is no blessed way, bigger projects typically include multiple different, incompatible result types.

[+] tentacleuno|2 years ago|reply
One of the advantages of libraries such as these (in my opinion) is it gives you the ability to incorporate the errors thrown into the return type of functions, creating a broader contract than you would ordinarily achieve.

This is mainly in the context of TypeScript (think IntelliSense, etc.), also considering the fact that TypeScript doesn't support typed errors (neither does Flow, FWICS).

Then again, wrapping everything is a huge pain.

[+] girvo|2 years ago|reply
Indeed.

A fun example of this is Nim: “results” is quite a nice library, but Nim’s stdlib and by extension most of its third party libraries are built around exceptions (or std/options).

And Nim uses effect tracking, you can encode the exceptions raised into the type signature, so in some ways it’s perfectly poised for something like “results” or unwrapit

Yet when you try to go down that path (like we did at work, doing embedded firmware) it feels like you’re fighting against the current, sadly.

[+] musicq|2 years ago|reply
This library provides a way to encapsulate errors into a Result type, so that any user want to retrieve the value, they must unwrap the Result first. In this way I think it will force people to realize that there will be errors, you need to handle them. For JS users, you need to call _result.value_, for TS, the editor and compiler will warn you.

In my opinion, it never tries to prevent people from handling exceptions using try/catch, instead, it enrichs the error handling solutions.

`wrap` function is just a simple way for users to adopt this Result type quickly. I think in projects, developers should create their own Result usually, can check this example.

https://musicq.gitbook.io/unwrapit/recipe/return-result-with...

[+] koenraad|2 years ago|reply
You are talking about a larger fundamental problem; code quality. This library can be a way to handle errors just as the try/catch block is. If agreed on within the team ofcourse.
[+] randomdev3|2 years ago|reply
Why make it complicated? The language supports catching errors, use that. You may not like it but that's the thing you have. Of course you can wrap errors, return [response,error] or whatever in your implementation of api calls etc. but you don't need third-party libraries for that.
[+] musicq|2 years ago|reply
The thing is not using try/catch, like the first case, sometimes you don't know if a function will throw or not. You can't tell from the function signature as well.

So this provides a way that told you the function you called might throw. It's more like an alert before a crash.

[res, err] is good, actually I used this style for a long time. `unwrapit` is a nicer way to let you write [res, err], like type hints and other utility methods.

[+] golergka|2 years ago|reply
Because errors have different semantics. Some should kill your whole app and propagate up to the top layer, where a transaction that wraps the whole http request will be cancelled, or a error screen will be shown to the user. But other errors are part of business as usual — they should be handled explicitly, and type system should force you to do it and match the right exception type.

Personally though I would go with fp-ts.

[+] thiht|2 years ago|reply
Agree with that. Exceptions suck, try-catch is an infamy, and errors not in the signatures or exploitable in any way by Typescript is a living nightmare. But what I hate even more is multiple ways to handle errors.
[+] satvikpendem|2 years ago|reply
Just use a library that already contains this and more functional programming idioms, like fp-ts or its successor, Effect [0]. It is a little more complex to learn but much more robust that simply implementing your own Result and other types.

[0] https://www.effect.website/

[+] tentacleuno|2 years ago|reply
Effect looks great. Strangely enough, I recall dreaming of it and recoiling at how much of a good idea it is -- can't wait to give it a proper go in a project.
[+] ikari_pl|2 years ago|reply
For anyone who has NOT written in rust yet, there's nothing in the readme that explains what's intuitive or easy about the library. It doesn't even explain what to expect as the `status` getting logged, not go mention other possible values.
[+] musicq|2 years ago|reply
Thank you for the suggestion! I will add that.
[+] mekster|2 years ago|reply
Why not expect that something would throw at some point where app logics are taking place and wrap with try-catch at your lower layer and handle uncaught errors there?

Adding all these boilerplate that not all people agree with would just make it harder to read and debug.

[+] solumunus|2 years ago|reply
In certain projects I can see why you would reach for functional style error handling, but for the vast majority of standard web apps/apis (what people are typically building in JS/TS) try/catch is far superior. Have the error bubble up to your endpoint, serve either a specific error message or a vague error message depending on error type, log accordingly. This is so simple, much quicker to develop and provides all the functionality you need. You can still use returned errors in the places that really call for it, but in my experience those instances are in the minority.
[+] tentacleuno|2 years ago|reply
Ironically, I bet that a lot of React-driven apps use try/catch on a regular basis. Despite not doing a deep dive into the topic, I believe React's "asynchronous" (Suspense?) functional components actually throw Promises, and the runtime awaits them before re-rendering the component.
[+] meowtimemania|2 years ago|reply
I'd prefer an api like this:

const [result, error] = attempt(() => someFunc());

This way you don't have to wrap all your functions.

[+] tentacleuno|2 years ago|reply
That seems a lot more ergonomic.

I can already see someone copy-pasting `wrap(myFunction)(args)` everywhere :-)

[+] vmfunction|2 years ago|reply
have you look at ts-fp, it is ts not js, but still.
[+] trungdq88|2 years ago|reply
Maybe I'm old, but I can't see any reasons why this is better after looking at the before/after example. This is adding unnecessary complexity with very little benefit (if at all).

If anything, the "try...catch" example actually look clearer and better than "!user.ok".

[+] redact207|2 years ago|reply
I think their reasoning is that it's type safe so the compiler complains that you didn't handle something that returns an error.
[+] jondwillis|2 years ago|reply
Is there a reason that typescript doesn’t/can’t add throwing function annotation syntax?
[+] tgv|2 years ago|reply
I would guess it is because just about every function can throw, and TS can't detect it with certainty, even for functions which are 100% TS. It also doesn't improve transcription or performance.
[+] eyelidlessness|2 years ago|reply
From memory: even if the arguments in favor were thoroughly convincing, it’s basically an insurmountable task to produce the types to cover even common runtimes, let alone the vast ecosystem of libraries (often themselves with community-maintained types). The failure mode for poor coverage of error types is worse than the failure mode of untyped errors, for instance because it would tend to influence where developers focus their error handling efforts in misleading ways.
[+] pyrolistical|2 years ago|reply
So a monad.

Btw zig also does this in a nice way IMO. You declare possible error enums and the returned value is a union of all the errors or the success type. You then use existing union handling at each call site

[+] quickthrower2|2 years ago|reply
This is sort of saying "exceptions were a mistake", right?
[+] qalmakka|2 years ago|reply
Exceptions are undoubtedly a mistake, because error handling is to important to untie errors completely from the code that generates them. That doesn't mean that stack unwinding doesn't have its uses.

For instance I find very convenient to use C++'s exceptions for truly exceptional cases - those in which I'd like the software to quit but I don't think aborting is a good solution (because maybe I want to properly clean up the application's state, etc). That's why Rust for instance uses C++ stack unwinding for `panic!`.

[+] musicq|2 years ago|reply
Spent sometime to complete the document of my rust Result like library `unwrapit` for JS/TS.

Still think this might be a proper way to deal with errors in JS/TS

[+] fearface|2 years ago|reply
I think the idea of try/catch is to let the error bubble up to the place where it can be handled. It usually results into having error handling in a few central places. Your example in github is IMO not how to make best use of try/catch.

Here’s lots of typical exception handling patterns: http://wiki.c2.com/?ExceptionPatterns

Persobally I prefer exceptions over boilerplate “if’s”, but good to know that there’s a wrapper for the people who don’t.

[+] slawr1805|2 years ago|reply
This is awesome! I dig this when using Rust and am pumped to try and sneak this in at work where we have a large TS codebase.
[+] optimistprime|2 years ago|reply
This is interesting. Does it have any performance impact?
[+] chmod775|2 years ago|reply
Promises already are a wrapper that can contain an error or a value, and using 'await' is basically unwrapping it.

This library adds very little value and is just another layer of abstraction which removes the syntactic sugar that was added with the previous layer and tries to re-implement stuff we already have (like responding to uncaught errors).

Just use base promises and .then/.catch etc if you want to deal with values/errors this way. Don't introduce another dependency that does almost nothing, and which others reading your code will have to familiarize themselves with.

I get that this makes doing some things slightly more ergonomic and less verbose, but at the end of the day it saves you seconds while costing others who have to look up documentation/code of yet another library much more time. Not to mention yet another dependency with sub-dependencies you have to manage. It wants specifically rxjs ^7.8.1 despite only using stable parts of that API.

I probably just spent more time evaluating this thing than it ever would have saved me.

[+] tentacleuno|2 years ago|reply
Promises are inherently going to be a lot slower than synchronous code (not to mention timing implications, re: the event loop, microtask queue, etc.). As such, wrapping everything in Promises is not a good idea -- reserve it for actually asynchronous operations.

(All of that's not even to mention the confusion around what operations in the code actually perform asynchronous actions.)

[+] the_gipsy|2 years ago|reply
That would make everything async which is not what you want.
[+] tills13|2 years ago|reply
My manager likes to ask "is it better or is it just different"

Well... It's certainly different.

[+] theogravity|2 years ago|reply
I'm not sure I agree with this approach, but I'd add instructions on how you would do unit testing when using this library.
[+] janpot|2 years ago|reply
Feels like there could be a place for a Promise.settled method that mimics Promise.allSettled, but for a single value.
[+] rswskg|2 years ago|reply
Will throw this in here as it's been useful for errors if operating with try/catch
[+] musicq|2 years ago|reply
try/catch has no problem, but the premise is that you know there will be errors been thrown. Say a function `divide`, you can barely tell that whether it will throw errors or not without looking into the source code.