Hello everyone.
Have you ever felt that Try/Catch is a bit inconvenient when developing an application in TypeScript?
I found an intersting video on YouTube that describes how to handle errors in TypeScript in a simple way.
I'm sharing insights from the video as a review.
If you have any other good alternatives to try/catch, I would love to hear them!
Defining the getUser function for error handling
First of all, I defined a simple getUser function to illustrate error handling.
It returns a new user with the given id.
const wait = (duration: number) => {
return new Promise((resolve) => {
setTimeout(resolve, duration);
});
};
const getUser = async (id: number) => {
await wait(1000);
if (id === 2) {
throw new Error("404 - User does not exist");
}
return { id, name: "Noah" };
};
const user = await getUser(1);
console.log(user); // { id: 1, name: "Noah" }
Error Handling using try/catch
Rewriting the previous code using try/catch, it looks like this.
const wait = (duration: number) => {
...
};
const getUser = async (id: number) => {
...
};
try {
const user = await getUser(1);
console.log(user); // { id: 1, name: "Noah" }
} catch (error) {
console.log("There was an error");
}
Problem with try/catch ①: It handles every error that occurs within the try block
The code below is not ideal.
Even though it's just a typo, "There was an error" is displayed in the console. I only want to handle errors that occur specifically in getUser within this try/catch block.
const wait = (duration: number) => {
...
};
const getUser = async (id: number) => {
...
};
try {
const user = await getUser(1);
console.log(usr); // ← There was an error
// ... (a lot of code)
} catch (error) {
console.log("There was an error");
}
Problem with try/catch ②: The Pitfall of Using let
Okay then, let's try to solve it using let
.
const wait = (duration: number) => {
...
};
const getUser = async (id: number) => {
...
};
let user;
try {
user = await getUser(1);
// ... (a lot of code)
} catch (error) {
console.log("There was an error");
}
console.log(usr); // ← ReferenceError: Can't find variable: usr
I got an actual error from the typo, but this code is still not ideal because I can accidentally redefine the user object, like below.
const wait = (duration: number) => {
...
};
const getUser = async (id: number) => {
...
};
let user;
try {
user = await getUser(1);
// ... (a lot of code)
} catch (error) {
console.log("There was an error");
}
user = 1 // ← ❌ It might lead to a bug.
Solution
It's much simpler and more readable, don't you think?
Furthermore, the user variable is immutable and won't lead to unexpected errors.
const wait = (duration: number) => {
...
};
const getUser = async (id: number) => {
...
};
const catchError = async <T>(promise: Promise<T>): Promise<[undefined, T] | [Error]> => {
return promise
.then((data) => {
return [undefined, data] as [undefined, T];
})
.catch((error) => {
return [error];
});
};
const [error, user] = await catchError(getUser(1));
if (error) {
console.log(error);
}
console.log(user);
Please take a look at the video, which we have referenced. He explains it very carefully.
I have never actually used this pattern in actual work.I just wanted to hear your opinion on how practical it is.Because it was discussed in his Yotube comments and I wanted to know the answer. I’ll be exploring best practices based on the comments.👍
Happy Coding☀️
Top comments (40)
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-...
Some comments have been hidden by the post's author - find out more