So, let's start with a little backstory about me. I am a software developer with around 10 years of experience, initially working with PHP and then...
For further actions, you may consider blocking this person and/or reporting abuse
Wrapping JSON.stringify is exercise in futility. You cannot recover from this error. Then why even handle it? Fix the bug instead of handling bugs and adding workarounds. You only need to handle errors if you have decided on particular fallback logic for the specific error. If you don't have specific ideas for fallback then you shouldn't have try/catch for that part of code. You also shouldn't need specific error messages on every single function call as stacktrace should be enough for you to be able to debug it. You only need 1 high level try/catch for any kind of unexpected error that shows "sorry something went wrong' to user" and logging/stack-trace that is good enough to debug and fix your errors.
Sorry but you are wrong. Handling the error at the origin of very different than debugging through trace stack.
Network errors, db errors, validation errors, file system errors, do not need a stack trace.
They might need a retry strategy, you might need to respond differently.
An smtp server error could be handled by retrying, using an alternate server or using a message broker to retry in future.
A top level only error handling is very bad and sloppy.
As you can see I am not really focusing on network errors but on JSON.stringify. The OP advocates blind try/catch on everything. Network errors sure need to be handled. Yet again probably not at every single case where fetch is used. Application should really only need one handler for network connection errors and feature/domain specific functionality should not be plagued with error handling for networking layer issues.
I strongly disagree. Even with Json stringify.
You get data from a database and fails with Json stringify. It should not under normal circumstances. A stack trace does not help, you need good logs,
I do not know if you have ever used the efk stack of something similar, but good logs are as important as stack traces
Your goal is not only to inform the user. Logs are your friend
Sure good logs are helpful too. I didn't said they are not. Agree that logs are necessary. But good logs is a separate question. Adding a message to JSON.stringify failure as in "stringify failed", is not helpful at all. The error from
JSON.stringifyis already useful enough as it saysUncaught TypeError: Do not know how to serialize a BigInt. Also stacktrace points to line where JSON.stringify is. If you just don't do touch it and don't try to reinvent errors there is already quite enough information to know how to debug the problem. Now it becomes hard when you start wrapping this with your own error messages and hiding stacktraces. In OP's article the stack trace will be gone and instead of well known error you will get custom error, and the way he's done it - because he's trying to remove all exceptions and replace it with railway programming - stack trace might be so screwed or non existent that it will be many layers away from where the error actually happened. And it doesn't end here the way he's doing it there might be no error reported at all and he might just return 400 over api confusing the API where instead it is a applicatoin bug that only deserves 500 response and alerts triggered. When your code is so screwed like that then indeed only excessive logging can save you from spending days of debugging the problem but even with them it might be difficult.Golang uses error wrapping, and unwrapping, and is very very effective.
Stack traces are for noon recoverable errors, mainly.
With errors as values you have
a. The ability to control if you recover or not
b. How to respond to the error.
c. If you wish to hide implementation details of not.
Generally, is good practice, each layer of your application to have it's own errors.
You can use error wrapping for some part of your app, for inner logging and some other errors for the end user to hide implementation details.
In any case,I really like the idea of errors as values.
Great article and good examples! I would also add a recommendation to design functions initially in such a way that the list of possible returned errors is clear. For example, through the Either monad.
You made me discover the Either monad :) Just another example that you never stop learning.
It's amazing! I think Either would be a great addition to your work. safe T for wrappers of already existing unsafe code and Either L, R for implementing safe code natively
More i dive into it, the more interesting it looks like, but this is like a material for another article :D It's not just a "few lines" more to make it work and make it understandable ;p
I've spent quite a bit of time thinking about and implementing these monads in TypeScript in a way that's really pretty and understandable. If you want, I will be glad to share and work on the article with you)
Yeah, that's sounds rly interesting. First i need to dive into this topic and see if this will suit me. But if this will be as interesting as it seems to be, i would glady make another article with Your help.
Absolutely fantastic article. Interesting way to handle errors - the need to remember to wrap promises in safe is rough, but the tradeoff gets rid of big try-catch blocks and messy error handling. And the Either monad comments are educational as well.
10/10 quality post.
Amazing article, thank you! :)
isExternal
I see in many cases of your code you are deciding to return error code
400. Yet in many cases I see that no user-error can produce it and they do seem more like programmer/server errors that should result in 500. And if they do result in 500 then you should consider to return generic error message, and log the error, to avoid exposing application internals over api.Why not use a package like true-myth? Particularly for fetching and API integrations, @zodios/react, @zodios/core & zod would work best together for type-safe error handling & return types handling & validation.
So safeAsync takes a promise. What if the function that was called to get the promise throws an error, before it gets to its return statement that returns the initial promise? Then the exception would happen before safeAsync has a chance to do its error catching.
nice but I prefer github.com/scopsy/await-to-js approach is more straighforward though
True, the general idea is almost the same, they have a wrapper that return a tuple with err / data. But for me i prefer the "forced" approched, which, if i am correct, they do not implement. You can access the response without checking the error.
So looking at the example in the repo, You can do sth like that:
Looking at my example:
But maybe their lib works diffrently, i didnt dive deep, just looking at example so i could be wrong.
It would be pretty easy to deal with exceptions through typed errors. Functions show be marked with the type of error they throw, and a function that can throw should be colored in IDEs so we know about it. Most of it this would be automatically inferred.
But this is something that would need to be added to TS, so monads are a good option for those just using TS
Good article, such JS error handling misunderstanding motivates me to write Rust-like library, since it's just better on a mile.
github.com/vitalics/rslike