>> and we all intend to put that try…catch block in place. Really, we do. But it’s not always obvious where it should go. And it’s all too easy to forget one. And before you know it, your application crashes.
Actually this is a good thing, it encourages you to make your code into hierarchical, tree-like structures. Then you have a try-catch at the root of each tree. Some apps have only one root (e.g. the program's entry point), others have a few (e.g. API endpoints on the server); it depends on how many kinds of parallel/async workloads your system has to handle but generally this should be obvious; if your system's entry points are not obvious to the point that you can 'forget' to wrap try-catch blocks around them, it's typically the sign of a bigger architectural design flaw.
>> Another thing to think about is that exceptions make our code impure. Why functional purity is a good thing is a whole other discussion.
Yes, it's a whole other discussion in which many developers might take an opposing stance. Personally, I think that co-locating state and logic is key to achieving modularity and clear separation of concerns; OOP is likely the best way to achieve that. I do think that functional purity is good in some cases, but not the majority of cases.
A lot of the large functional systems that I've seen were highly deterministic, highly robust spaghetti code; they are difficult to make sense of and can become impossible to maintain.
I prefer languages that allow you to build abstractions like this, rather than being beholden to the standards body.
> Actually this is a good thing, it encourages you to make your code into hierarchical, tree-like structures. Then you have a try-catch at the root of each tree.
That leads to a complete lack of error handling, because you've lost all of the relevant context of what you were trying to do. It's attitudes like this that leave you with a dialog box that says "an unknown error has occurred".
Of course, it takes some discipline to come up with a consistent and well defined error-handling strategy, but IMO it is not difficult. Also it's not a problem if a third-party library doesn't agree with your error-handling approach, it's often a good idea to re-create and re-throw errors from third-party libraries to provide more context anyway. That said a lot of JS libraries do use a similar approach as me for error handling, but sometimes they might use an error.code property instead of error.name but it doesn't really matter.
The worst is if a third party library throws plain strings, but that's also often not a huge problem either because my own logic can make sense of the context and re-throw a proper SomeThirdPartyLibraryError which incorporates the original error string; it's just a little bit of extra work.
For many errors, "an unknown error has occurred" is the right message, since it's a bug. There's no action the user can take to fix it, and you should let the user know to try again later or contact support. The message can then be sent to your error reporting software so you can fix the bug.
Sure, you can explicitly call "An unknown error has occurred" dialogue on every error case, but instead, I think the ideal tradeoff is to handle all errors that you predict will happen regularly, (ex. 404, authentication errors, etc.) on the value/monad level and handle errors you don't expect happen via the throw level.
Disclaimer: This is a variant of my Java Exceptions vs. Go err conversations. I'm working Either into it.
The Go-like approach can often force you to handle errors at the lowest level. You often have details of the error but you're without context of the program. An example is a zlib library; you don't want to handle the failure of any particular `file.read(buff)` call but rather just fail at a higher level `unzip_file(src, dest)`, and with context make choices in the program from there.
Now you can do this in Go as well, you just manually unwind returning err to the level above. It's verbose compared to exceptions (but more explicit...ish). I suspect most programs will end up resolving things at about the same level.
That's the thing with Either(), since it's doing the catching you clearly have exceptions to work with too. In terms of error handling the interesting bit is what 'tier' you handle things at, the Either vs Try/Catch are like 80% the same.
/me waits for arguments to ensue
20 years ago, maybe. These days every browser has a debugger which will automatically take you to the exact location with full context at every layer of the stack and tools like Sentry will collect and aggregate those reports even from devices you don’t control.
Also the world is a whole lot bigger than frontend dev.
Yes, monadification makes it easier to construct new functionality out of tiny parts, I'm not arguing with that at all. What it costs though is more than performance and debugability, but just simple readability goes right out the window, as a rule.
Edit: removed ill-thought-out remark.