Hello everyone.
Have you ever felt that Try/Catch is a bit inconvenient when developing an application in TypeScript?
I found an intersting video...
Some comments have been hidden by the post's author - find out more
For further actions, you may consider blocking this person and/or reporting abuse
It is bad design, it is like going back from exception to C style (error, result) returns.
If this pattern was useful, exceptions would never be invented.
When you nest your code, you need to rethrow the error as method has failed and result is undefined.
Such code will lead to nightmare as one will not be able to find out why result is invalid and error is not thrown. Console log doesn’t help for caller.
Exceptions were invented in such a way that top caller can catch it, log it and process it. There is no point in changing the calling sequence and rewrite same logic again to virtually invent new throw catch.
Errors and Exceptions are not the same thing. You don't have to like it, but typed errors is a very good discipline.
I have had experience of following such pattern and it often leads to undetectable bugs.
If you don’t consider error and exception as same then you are inventing a third state, which in combination of other conditions, it just increases complexity in the flow of logic. And I had to undo every time I used this pattern.
that's state of the workflow, no need to be error type
Finally a person on internet that says the truth behind the horror of bad error handling in Golang, and now Javascript and Typescript if people don't study well how to do good programming.
You as the majority of developers, think that good programming exist, when good programming is subjective to the person reading and writing it. So basically the ALL the programs you wrote are bad programming from the computer perspective, and might be bad for other programmers too. When you understand this, it is a life changing because you think less about good and bad to start thinking on patterns, which if is bad or good doesn't matter, just follow it blindly right? Wrong! Programming is a form of express yourself, that's teh beauty of programming, God damn! Stop bullying bad programmers bro
This is an interesting idea. But over my many years of coding, I find try catch very useful… some might disagree but I kinda see it as an advantage to other languages… maybe more like the panic in Go.
In my use cases, my apps never break, errors are handled elegantly, proper notifications are resolved… although, I use this pattern even in TS and haven’t come across any blockers.
But for sure I can try your idea and refine it but it would most likely complement the try catch syntax and advantages rather than replacing it.
I like the syntax that it creates and the inspiration from Golang and the
?=
syntax that has recently been proposed and is in very early stage.I do wonder about how much benefit this has since you now have to have 2 conditions where the error is handled, one in the catch block and one in the if block.
If you are wanting to suppress the error but still add a log of some sort, you can simply use a combination of await / catch
This is less code and allows you to avoid using let
I basically agree with using
await
and.catch()
.However, If you need to make a decision based on the error value, the syntax removes duplicated logic and enforces a consistent pattern in your code.
That said, I have never actually used this pattern in actual work.I just wanted to hear your opinion on how practical it is.
Thank you for your suggestion.👍
I don't quite see the advantage of this over using
const user = await getUser(1).catch(console.log);
In what cases is your suggestion better?
The advantage is more clear when you need to do more than console log the error. If you need to make a decision based on the error value, this pattern removes duplicated logic and enforces a consistent pattern in your code
A consistent pattern doesn't provide good properties to a program by itself. You could be doing assembly if that was the case.
This is a great breakdown of the limitations of try/catch and a more elegant solution for handling errors in TypeScript. The
catchError
function looks incredibly useful for maintaining clean and readable code.@programmerraja
Thank you for the kind words! I’m glad you found the breakdown helpful. Yes, the
catchError
function is indeed a handy way to manage errors elegantly in TypeScript. It really makes a difference in keeping the codebase maintainable and easier to understand. If you have any thoughts or suggestions on improving the approach, I’d love to hear them!there is no problem with try catch and your suggested "solution" is actually looks more like a hack ... why not make typed catch ?? like it is in java that way you can catch exactly the error you need to .. is not typescript all
about being "typed" javascript anyways ???
Typescript doesn't handle any type of runtime typing. It's goal is simply to introduce static type checking to a code base.
What you're proposing would require some sort of runtime type differentiation. Which I suppose could work if all errors had a different type. Unfortunately that's likely not the case in most instances. To further compound the issue there isn't even a way to ensure the only thing being thrown is an error. Which is actually the reason the type in the catch block is of type
unknown
and noterror
.With all of that said, using the result type pattern would likely be a better way to handle this. Localize the error handing, then return a result that indicates if the operation was successful.
Instead of combining the worst of two, embrace Result type (also known as Either). Return your errors explicitly, handle them explicitly, don't throw exceptions.
Promise type is close to it, but unnecessarily combines Future with Result. Conceptually, both should have been independent, and Promise would've been Future> - e.g. a value that might eventually become a success or an error.
Then you'd await it to get Result, and pattern-match the Result to handle your error. It also removes the possibility of accidental error.
To add onto this, there are some TS libraries that implement this (and other algebraic types)
gcanti.github.io/fp-ts/modules/Eit...
effect.website/docs/data-types/eit...
Or you could roll your own:
type Success = { data: T, success: true }
type Err = { success: false, data: null, error: any } // can type the error if desired
type Result = Success | Err
async function tryPromise(prom: Promise): Result {
try {
return { success: true, data: await prom }
} catch (error) {
return { success: false, data: null, error }
}
}
ex:
const res = await tryPromise(...)
if (!res.success) // handle err
const data = res.data
Open comments just to find this one. 🫶🏻
I have just realized that dev.to ate the part of my message, confusing it for markup: "and Promise would've been Future>" should be read as "and
Promise<T, E>
would've beenFuture<Result<T, E>>
".So we're back at good old Nodejs style error handling? Although this might solve a few issues, it feels for me like it goes only half way in the right direction. While the article explains well some issues with try catch, it actually doesn't offer a solution and only a half-way mitigation, because in the end, instead of having no idea about which error hits youin the catch block, you now have no idea which error hit you in the first array element. I also think, using a tagged result object (like
{ ok: true, data: T } | { ok: false, error: E }
) is a bit nicer here, as it offers some more room for extensions on the result object.Personally, I am satisfied with native JS try/catch implementation over abstraction. However, yours feels like a step backward, remember the good old callbacks that got the error and response as arguments? Either way, I believe there are better ways to handle your problems:
1) Move the try/catch to the appropriate location, rather than a chain of try/catches, and let the control flow take care of things for you.
2) Don't want to have response checks everywhere? I am not talking about the fetch specifically, but more of the pattern in general. It is better to move those further upstream with meaningful abstraction.
3) Building on, we can also do more of a utilitarian approach (for the try/catch haters out there):
4) Finally, modularize!
With this, you are still following modern javascript sugars, meaningful abstraction, less cognitive overload, less error-prone, zero try/catch, etc. This is my code style. If you were on my team, you really don't want try/catch, and I was reviewing your PR for this utility, this is how I would have suggested you write the code. I'd greatly appreciate it if you could look at my suggestion as a pattern rather than an answer to the specific problems you presented. I welcome other devs to suggest different styles or improvements to my code as well. ❣️Javascript!
Great article. Thanks for elaborating on this topic. The try/catch format has limitations and introduces more clutter. I myself also faced the same problem in PHP. In my concept, I implemented Result type from Rust to return either an error or a value.
dev.to/crusty0gphr/resultt-e-type-...
Inspired by golang error handling 😀👍
@dhananjayve
I saw that mentioned in his YouTube comments as well. I haven’t used Golang before, but I’ll look into it. Thank you!
You could write the same code and still use a try/catch.
Nice! In fact this approach is the basis of TaskEither in fp-ts package, check it out - indispensable!
Syntax is good. I found it similar to how RTK Query has structured their data fetching. Same "function" gives both data, error and a loading state (you can add a loading state here too, if the given operation takes some time)
This looks like go error handling works. Returns a tuple for error and value.
You still have to type check the error here tho. Your syntax error is a bad example.
You can also wrap the try catch in a function that can throw error or return values and can catch each type.
OP shows innovative thinking and tries new things. Props for that. This is the only way new and better ways are discovered!
Before my sincere reply, first some fun:
No hahaha
Jokes aside, this approach has the potential to make things a bit more graceful, but nothing that can be accomplished with more traditional means.
The price you pay for the new error handling is offloading it onto how you interact with your code. It makes things a bit more convoluted and more complex to read.
vs
Perhaps it makes more sense in your specific case. However, this exercise is somewhat unnecessary in a RESTful or Handler-oriented environment, such as Fastify.
Extra thought: Could you offload conditional error handling to the catch block? It's not as graceful, but it won't interfere with your invocation.
Sorry, I accidentally submitted the comment and had to edit it quickly.
Since a lot already shows up the pros of using this approach, I'll try to provide the cons.
This is good if we are not doing any kind of error management, which is transforming different responses from different backends into the app's error structure.
I once does this kind of error handling, using generics, just in different language, eventually, i think the most readable way is to not abstract out simple logic like this.
Instead, if you encapsulate each function / use case of the app in a single function, you can have some kind of independence in each of them, leading to better readability.
My approach sacrifices DRY, I'm aware of it, but I think being too DRY can also impact readability in some ways.
Readability as in "when I need to understand this logic, I need to read multiple different logics that the original logic might depend on".
Hmm.. you need to call
catchError
every time you request data from the remote? It seems not graceful. This kind of error-handling logic should be put into the HTTP request service rather than in your business logic.Don't do error handling locally. Let the error flow through to be caught by a universal error handler. Especially in .NET where you can have a middleware handle errors. You shouldn't endeavor to write error ceremony every time you want to make a call.
It doesn’t improve much callback hell case.
This is a horrible approach. You are literally using try catch, it is even in your final code. Also you are wrapping your code into an async task with all the additional objects which is quite an overhead. Also, as a side effect you are using exception handling for a kind-of flow control which is a big no-no, antipattern.
Looks like golang. But from my point of view golang way is better because utility values are placed after main one.
We've tried that approach combined with named tuples on our project, i like it.
But i think i will stick with try/catch block 😂
If you're decent enough to be able to write good code, you'll never need such a workaround.
I do something similar. I recommend though that you use null instead of undefined as your non-error. Undefined should be avoided unless you have a good reason (i.e. an edge-case reason) to use it.
This is called a "monad" - a concept from functional programming where the function return error or result