Unwrapit provides a way to handle errors in JS/TS
musicq.gitbook.io
musicq.gitbook.io
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.
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.
A good example is when working with Qt, trying to avoid QString and QObject as much as possible is a fools errand, its slower and ultimately ends up with ugly code.
"Law of least surprise" is a good thing to keep in mind when writing maintainable code.
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.
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...
Hopefully there's an ideal world for you.
More like living in a society with invisible cars. You only know there was a car when it hits you. And every time you move a foot you have to first try to throw a pebble to check if there's a car
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.
Personally though I would go with fp-ts.
"recoiling" implies a negative response, ie revulsion or fear
Adding all these boilerplate that not all people agree with would just make it harder to read and debug.
const [result, error] = attempt(() => someFunc());
This way you don't have to wrap all your functions.
I can already see someone copy-pasting `wrap(myFunction)(args)` everywhere :-)
https://musicq.gitbook.io/unwrapit/recipe/return-result-with...
If anything, the "try...catch" example actually look clearer and better than "!user.ok".
function libFoo(foo: string): throws FooError;
function libBar(bar: number): boolean;
Both of these functions throw. With untyped errors, you have to assume that this is at least a possibility. Now you have every reason to assume it’s safe to call one of them without handling errors.The argument as I understand it, which is convincing, is that types like this are inevitable if typed errors were to be introduced. The scope of even well typed code with no documentation of even the errors thrown directly is enormous. The scope of code which might propagate errors from calls deeper in its stack is unimaginable. And there is basically no way to do static analysis to meaningfully reduce that space.
It could be fair to say this was a missed opportunity earlier in TypeScript’s development. But introducing typed errors retroactively would lead, trivially, to millions of cases like the above. Many in very hard to find ways.
Could those types be produced and refined to be as high quality as existing types (first party or community provided)? Of course. But it would take years. And unlike introducing types where none exist, the gradual story just doesn’t exist. You’d have to treat literally every function and property access as `throws any` to get safety equivalent to the gradual types story for non-errors.
And the incentive to type one’s own code with errors is hard to imagine: how can I, as a library author, say with any confidence what anything I write throws, if I have no idea what the underlying code throws (or if it really even does)? Why would I take on the maintenance burden of getting it wrong? Or incomplete? Or incomplete and wrong?
Even if it were an opt-in/progressive adoption keyword like “throws” that enforced some compiler guardrails, I think that could be a huge win for reducing unexpected runtime exceptions.
function arrmin<T extends {field: number}>(a: T[]): number {
let m = a[0].field;
for (let i = 1; i < a.length; i++) {
if (a[i].field < m) {
m = a[i].field;
}
}
return m;
}
Now call arrmin([]). There's no way to avoid a throw. You would have to annotate every function that uses field selection on an array element (and a bunch of other conditions) as "throws", or enforce error checking code (if (0 <= i < a.length) {...}).Rust didn't solve that either, does it? It just panics.
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
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!`.
Still think this might be a proper way to deal with errors in JS/TS
https://github.com/musicq/unwrapit/blob/d5c437a235ad1f5ad042...
There's a lot of more established and battle tested libraries with the same functionality - I've recently been using Purify and finding it to be absolutely fine :)
their version of the same feature is here: https://gigobyte.github.io/purify/adts/Maybe
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.
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.
(All of that's not even to mention the confusion around what operations in the code actually perform asynchronous actions.)
Not that it matters - if you're going down this route either way, performance is not your priority. If you really care about performance, don't use either if you don't have to.
Unnecessarily using Promises introduces further work for the VM, plus it may cause issues with race conditions, wrong variable values (which aren't easy to debug), etc. if you don't hold a magnifying glass to your code.
To be clear, I'm not benchmarking OP's library -- just providing an important note on why you shouldn't use Promise as a general monad. There are libraries for that: maybe not this one, maybe nothing at all. Do whatever works for you :-)
We're clearly talking about actual CPU time spent here when comparing code using those approaches. We're not interested in what runs first in some software mixing synchronous and asynchronous code. This is completely irrelevant.
Perhaps I missed a part of the conversation? I began by highlighting the issues with your approach regarding the event loop, and you started talking about the implementation of this library in particular, seemingly as a counter-argument?
To be clear, I'm not disagreeing that wrapping things is slower than not wrapping them. That's quite obviously true. I'm just pointing out bad advice in your parent comment.
Well... It's certainly different.