This was also my reaction when I discovered there was this weird concept called "learning" that lets you find new strategies that you like better than your old ones.
That's what drove me crazy: async/await is syntax sugar that produces the exact same Promise. The error handling function could have easily been written as an async function with a try/catch block.
Yeah it’s the exact same thing, but this creates reusable functionality. Otherwise you’d have to reimplement the same function for everything that handles promises.
@@dio56301 I doubt you're missing anything other than preferences. I can say, when I first started coding try {await x()} catch {} was easier to write and understand than .then(). That being said, this methodology in the video is fascinating and I will have to give it a try.
It's interesting for sure. Although I think all we've really done here is inverted the try-catch. You're still handling the error at the top with if(error), and the else block is like the old "try"
@@ojikutu Not really because he's basically just moved the scoping to the else block. You wouldn't want to access the user variable outside the else block anyway
@@risitas5874 That's literally not how scoping works or what that means. Besides, as he also pointed out in the video, the only reason he is using the else block at all is because he was not inside of a function and could not early-return.
It’s fascinating to explore this possibilities, but I strongly recommend sticking to the try-catch approach 99% of the time, reserving the techniques discussed in this video for a few highly specific cases.
Why though? There a huge library building on this concept (EffectTS) and golang does this already by default. Also the inconvenience of try/catch is one of the two reasons why people still write: data = await fetch(...) result = await data.json() This should throw a compile error and be red in your editor, but it isn't. JS/TS should know that this throws atleast 2 errors and expect you to handle them. There's even a TC39 proposal to add this as a new keyword (behaviour). While it doesn't solve the issue that the language doesnt know what could throw (EffectTS solved this as best as it could), its still very encouraging to actually handle errors properly with this. So i'm really interested in why you would not recommend it...
in my opinion this is not the best advice, because this is idiomatic with Go where it forces you at a langauge (& LSP) level to consume the error returned in some way but forcing this idom into typescript - which dont get me wrong i have done similar in the past as well - risks inconsistent implementation which means error states not interupting the control flow in the right way at the right times. Forcing idioms doesn't always make sense even if they yield nicer syntax. EDIT: Also worth thinking about the several ways that error propagation can 'escape' the promise chain depending on what async you're doing (not just promise based), whether its unawaited+not returned, using timeouts that aren't themselves promise wrapped and awaited etc, which would make your errors evade your generic catch block, ending up probably at the process level or at least far away from its lexical context. Then you're either doing a bunch of nested calls to your wrapper, or just doing some nested .catch or even worse relying on process.on() captures, or something else depending on context, which ends up with a messy mix of paradigms. Imo it's just better to stay within the language idioms for predictability. Just my opinion though. If you want to write in languges that reliably return errors, and force you to consume those errors, use those languages. Typescript might have foibles in errors particularly in some async operations but this just seems like asking for trouble in production code. In other words, idioms exist in langauges in the context of how that language operates. Just because you can replicate and 'paste over' 90% of the behaviour through functions in another language, does not mean that you should. That missing 10% is going to cause hella problems and if you work in a consequential domain then that's not really a good idea, just for the sake of some opinionated preference on syntax. Just learn async really well, learn the event loop, handle it all idiomatically, and you're golden.
it's a different perspective, it makes it a little bit easier to write the flow of the logic without try catch, but you would need to handle the error flow yourself
I somewhat agree. It's still JS, and as long as you can enforce this pattern using a linter rule and CI job, any developer who isn't completely useless can learn the pattern intuitively in literally an hour.
3:00 that isn't the only way to deal it it. You can use tools like "instanceof" in the catch to determine what type of error was thrown and deal with them in different ways just like you ended up doing it in a convoluted way.
Agreed. The only thing he's solving is not having to define the variable as a "let", using a "const" instead. But a const with extra type checking after is infinitely worse. Besides, if you write proper functional code, you usually want to return the value at the end of your try block, in which case you can just assign the value to a const wherever you wanna call the function. So in reality, the situation he's trying to solve it not even a valid one.
The catchError funcion is exactly what golang does. I tried to introduce that in a previous job and I got so much push back that I had I to remove all of it. The only annoying thing is that, unless you use let, if you have multiple of those calls I the scope, you'll have to name the error const something different in every call.
@@chris94kennedy Sure, it's not native to the language, and golang doesn't return an array, it can return multiple values in the same funcion. But in terms of flow, it's pretty much the same.
@@ericmackrodt9441 no - it is not. Please go and read my main comment on this video where i lay out why this is not the same - despite *looking and feeling the same in many/most cases* as golang.
"Ew, we have to move things into this try/catch. That's too much work!" /proceeds to write a complicated generic function that mixes results with operation state
@@johnridout6540 Yes, im thinking of implementing this right now. I was quitely annoyed at the try catch situation i had before and this is a good solution.
Thanks for the video and great idea. I have to use a lot of trycatch in my job so this was really interesting. I would really like a video on Effect as it looks cool
If you have small functions which only do one thing, then you won't have a bunch of code tryign to interact with your user right there immediately after you fetch it. Your try catch can just be in the fetchUser function.
But then you have to somehow tell in the return value that what you returned is coming from an error. I know you can let this slip sometimes but this approach can't be used universally. For example - say I have a method that gets something from the server and this something is an array of some items. Yes, you can always pass an empty array as a fallback but that does not say anything about the way of getting the data. What if you want to let user know that there's been an error getting the data and that's why they're seeing an empty list? Sure, you can display the message in the try/capture block of this method but unless you have some kind of centralized error handling, most times this will not be the case.
You can simply add add bracket for the let scope variable. Try catch not meant to catch all error except the outermost one. You catch known errors and handle it accordingly. If you comes from Java you know that's well how Try/Catch is suppose to work. The exception is try/catch need to carefully handle the asynchronous situation.
The correct solution is to try catch the fetch statement and return undefined if it fails instead of the user data. What you're suggesting instead is a hacky version of a callback statement from the era before promises. We moved past that for a lot of good reasons. One of the problems you are going to face is if you do this twice in the same block of code and now have two variables named error. So now you're naming your errors things like error1 and error2? What is this recommendation.
Congratulations; you just reinvented the Golang much despised error-handling pattern! (which I happen to appreciate and prefer.) And I mean congratulations sincerely, not sarcastically as try-catch used throughout a codebase is the root of all evil.
This doesn’t make sense. We have built ins for this. Use a try/catch and rethrow the error. You can provide a cause in the options if you want access to the original. In TS you can cast the error as unknown and you’ll be forced to check the type too. Sure abstract that into a function if you want something reusable but use the languages patterns and conventions
thank you for a sane comment. use the idioms that exist in the language because those idioms rely on the underlying mechanics to actually make sense. Just because you can get 80-90% of the way towards replicating a language behaviour from go in typescript does not mean you should, there are many ways this guy's code is going to go wrong. See my main comment for detail. It's sad to see a so-called senior engineer promote this imo because half the comments are like 'wow thats amazing im going to adopt this'. Enjoy the footguns.
So how would that abstracted function look like exactly? I am genuinely trying to understand. I understand you prefer a nested try catch over the Promise.then, but what is you return type exactly?
@@chris94kennedy a good idea will spread across languages until it gets native implementation. I am not familiar with Go syntax but I found this abstraction readable. Again you are complaining about footguns that already exist, whether you use this approach or not. What exactly would you suggest alternatively?
@@Kriszzzful i wrote a full separate comment on this video that makes my points so i dont feel the need to rewrite it and I'm not talking about syntax, in fact to the contrary ive said that that is the benefit of this approach but leads to issues. I have already made a suggestion so not sure why you're asking for it again, I said learn all the different forms and nuances of async in JS and how to write idiomatic error handling for all the edge cases.
Many great ideas that emerge after entrenched dogma appear not to make sense to the majority at first so the majority are very critical when first proposed. But then more and more people start to consider that maybe the new approach is in-fact better than the previously assumed best practice. Over time, the majority go from being critics of the new approach to becoming advocates as the approach becomes mainstream. I watched this happen with classic object-oriented programming vs. containment and delegation, and I expect to see the same happen over the coming years regarding exception handling (try-catch/throw) vs. errors-as-values. #fwiw
This is useful in an abstraction like your react-query fetcher that can give you the data or handle the error headers/body for you. For everything else try/catch is easier to read
Thanks for including the types! There's pros and cons to wrapping error handling in every request. I usually end up having a single try catch but the catch calls the application error handler with 10 or so Error types.
Effect also has an "effect/Micro" component that seems like a reasonable choice if we're mostly trying to get the benefits of the core Effect type and not all of the other powerful offerings that come with the full library. I'm still getting a feel for Effect since fp-ts announced the merger with it.
I see try/catch as few critical unexpected errors and limited scope, known errors should be handled with if, userExists(), result is null etc. It feels wrong to overengineer or add more libraries into the picture when you can simplify instead.
I saw that Theo showed something similar and this comes from go which manages errors this way. You explained this really well and I will copy and adapt your code to my needs. Thanks 🙏
It all started with treating "not exceptional case" as exception. "User does not exist" is a regular workflow. Exception is when you can not connect to the server. From there it all went south. Some weird spaghetti code with promises instead of good old try/catch. Exception is designed to handle the error and exit the function. If you catch it and continue, something is fundamentally wrong.
Very similar to the proposal for the safe-assignment operator, which has been modified to try-expressions instead. So we might get some syntax for something like this in the future.
Try catch is a low level pattern and can be used perfectly for catching errors. But, you have to type your errors. In that case you get multiple catches for every error type or an if statement in your catch to check the error type.
Or std::optional in C++ that was from Alexandrescu's 2001 book that became the basis for Result. But I think this is less good than either, and more like Go's error handling, but also worse than that. Interesting, and possibly useful. More like a change of pace than any real benefits.
"try and catch" role is to catch Exception, "unexpected behavior" like network loss, i/o problems, etc.. Not for typo or error made by developer. In PHP, if you try to print_r() a undefined variable, it will raise an Warning error. In Java, because of compilation, your code would not even compile at all because to try to do something with undefined variable. In JS, in your example, it will failed because your variable is defined in the scope of the "try and catch" ... again, it's a language feature ... So, the problem here are about the pro and cons of how scopes are generally handled on the language level (just compare php/js/python per example). Until TypeScript become a real language on its own, you'll have live with the fact that is JavaScript is behind executing it ... But, I love it for what it offerts :)
The motivation in this video is to have specific error handling depending on which of the called functions failed. We do this in the big common catch block by testing the error class (if necessary). Writing log output is already done in the called functions. The only distinction on caller side is whether to retry or whether to continue with a warning vs. rethrow the exception.
This is not an issue with try catch, it's a problem with how you think about architecture, usually no one would store a global user outside their function and set it's value later, you could've literally had the try catch inside the getUser function and return a user from the get user function and do any other error handling in the catch block, when you're writing actual business logic, that repository code will obviously be talking to an external service and "returning" something, not setting a global variable, then some other function in your application logic will call the repository and the getUser will return a user type, if there's any specific error handling try catch will still do the same thing, what you did was to write boilerplate for wrapping then catch
This is like one of those infomercials in where clumsy people dont know how to use certain appliance or device, and then comes the savior with a tries to solve a problem that no-one has
@@Gakai08yes, error handling *feels* better in rust when compared to go. But both the languages embrace errors as values approach, so it's not that much different, just different syntax or pattern.
Supabase's API works kinda like that. I dont dislike it, but it can get messy with multiple promises with multiple arrays with errors. I guess the best solution depends on your project.
After working in go for a bit, I've come to like the multiple return values with the second value being an optional error. Just check if the error was returned and handle it there. if you return a value regardless of an error, it makes the code even safer.
It's the same way the supabase JS SDK handles this issue. They return an object not array but otherwise it's the exact same idea! Great to see how it actually works for anything you want it to, rather than a specific SDK!!!
This is a long standing debate. Errors as throws or errors as values. If you want to adopt errors as values in javascript, you definitely can. Golang, instead of re-throwing, just bubbles the error, returning it. You can use this at the top level of your throw-rethrow stack, where you will not rethrow the error but handle it. I personally love the errors as values, but everything in Javascript ecosystem is throw based, so it will be a little bit hard. Of course if we get the ?= operator, then things could change.
Forget all the whiners, this great. I hate dealing with the scoping issues that come with try-catch, and I hate that in typescript, the error is basically untyped which, in fairness, it may actually be something you can't actually predict, but I'd rather know if a function call can throw or not, and kinds of errors it can throw.
Cool, but maybe a nice usability addition would be to have a result type (object). Returning an array will inevitably pollute the variable space if you need to use the catch function several times, you will end up with err,err2,err3, etc. You could also declare the results as variables instead of using const, do I don’t know if that is a thing
You have GOT to be the closest thing humanity has to the perfect video creator. Clear, concise speech, beyond perfect presentation, fully planned and prepared lessons, and material so well written that it's mind-boggling. As if that weren't enough, there is also unparalleled depth of knowledge and understanding. A video of yours is the de facto standard for all others. NOBODY matches the quality of your videos on every conceivable front.
I've been working in web dev for a few months and I never had a problem with this kind of thing. Though we defined a similar function (tryCatch, which literally tries and catches the callback you pass to it) just to have a more modular code
Practically there should be error handling with try/catch in the getUser function to begin with and that would have solved all the problem you mentioned.
To the ppl saying this would lead to having multiple constants named error, we could return either an Error or a result instead of an array, and verify if result instanceof Error or not
@@adtc It doesn't really take a genius to discover this - I've been using this for years already. I do agree - lately I've been seeing more videos about this subject. It seems to be easily milkable so I guess it's because of that. But hey, it's a nice pattern so spreading the word is beneficial.
Yeah No. This trend of ditching try-catch is funny. I remember the good old days when errors by value was big no no. What happened to keeping business logic separate from error management ? The main reason I don't like Go. No try-catch.
😆 LoL, 🤦♂️ what a funny guy. The main reason try-catch sucks is when it's frivolously used to mitigate foul logical constructs, otherwise it's very necessary for errors that occur beyond your control (mostly external input).
At 1:52 it’s slightly false. You can define a `user` outside try, then affect inside, then exit main path on the catch block, while keeping main path intact. 😊 (Edit: Talked about at 3:02, nice.)
I've always wished languages had something like a data type of error or a class of error that had the special property that if you test against it, it always returns false. So a function call would return the normal value, or the error. And you just need to test against the return value to see if it worked. In cases of errors, the object would have properties for the error message, a stack trace, etc.
It's just a basic abstraction. I always use something similar to wrap my error sensitive block. It makes a lot of sense in middleware to just pass the error to a handler afterward. If not, it is just adding more control flow.
There are other even subtler tricks that a real developer would know about, but typeshit will never let you even get near those edge cases, it is by idiots for idiots, playground guardrails.
isn't it essentially the same as putting a try catch in a separate function instead of writing the logic directly in the current function though ? I mean, I like that it make the original function more readable but you can write the very same logic with your withCatch function being async and using a try/catch/await inside just like in your original function and simply return the same thing you returned in your promise. The point is what your approach didn't really change anything about the usefulness of a try and catch, it's just moved the logic on a separate function but try and catch can be use in that separate function just the same. async function catchError(promise: Promise): Promise { try { return [undefined, await promise] } catch (error) { return [error] } } const [error, user] = await catchError(getUser(1))
reminds me of effect error handling (from the functional effect ecosystem of tools, not the react hook) Edit: now that i watched till the end, I can see you mentioned it too 😂
The video is well-explained, but the approach really depends on the nature of the task. For simpler tasks with fewer sections, using a basic try-catch in each section can be an effective and straightforward solution. However, in extremely complex situations, using catchErrorTyped with await works very well, as it provides more control and clarity in error handling. It’s about using the right tool for the job: just as you’d use a knife to cut vegetables, not a sword, a simple try-catch can be more suitable for simpler needs.
Personally if the function is one of my own - like a get_user() abstraction of a database call - I like to return errors in the form of an object like `{ error: Error_Enum; data: T }`. To handle exceptions the ORM or any other applicable external function calls might throw I definitely reach for a catch_error() style.
The reason you gave for not using a try catch doesn't make sense at all. While using try catch and logging the error you can print the actual error message by console logging the error plus your custom message. eg catch { console.log('Custom error message' : error)}. Your method is very obscure, I thought you were supposed to simplify the web for us, Kyle.
Every time I see this it reminds me of this funny Go joke: Go always keeps two cups by the bedside. One cup has water, while the other is always empty. Rust asks puzzled: "Why do you always keep two cups here?" Go replies: "Because I need to drink water when I wake up at night." Rust follows up: "But why keep an empty cup?" Go: "What if one day I wake up and don't want to drink water?"
Thanks for helping me understand try{...}catch{...} better. :P I'm not sure if I will use this solution. I don't quite understand advanced fix yet but the basic fix is interesting, and it's reusable.
@@ring11037 No. I'm just lazy at writing my explanation so I told you to read what other people say so. 🤣 Personally, I don't want my function calls wrapped around something like catchX(myFunction). The try/catch is enough if you know where to put it. Routers also have a "next" step func and catch all error handling. What he's showing here is just snippet of code not the actual arch design of the app.
Despite the comments. Things are moving in this direction. Plaid TS SDK returns { data, error } everywhere. They never throw errors and so you never need to catch. This is the way. Is this like Go? Yes, because Go is superior. However, when you can't use Go there is this approach.
Sure it's a better experience, but the language doesn't force it so almost no one will do this. New projects using this will eventually get developers who ruin the pattern, and now you're left with some places where errors are handled like this, some places with try/catch, and places where error catching doesn't happen at all This needs to be part of a library to be useful(I agree, effect is too bloated if you just want this), and there needs to be strict linter rules to prevent people from doing it any other way.
1. If your code blocks inside `try` are too long, then your code is doing too much. Refactor. 2. The `Error` class can be easily subclassed, so you can throw application specific error types 3. With subclassed errors you can easily do `if (error instanceof MyErrorClass)` and then rethrow the error if it doesn't match 4. In the case of TypeScript, your `usr` syntax error will get caught by the compiler. This is more applicable to pure JS. 5. Even in JS, your model won't catch a syntax error *in the called function* - it'll still throw that generic error.
1. He did refactor 2. He also did that 3. He also did that, and abstracted it nicely 4. Syntax errors were used as an example. Additionally, TSC is not actually a compiler and is far from perfect. Unexpected runtime errors are very real and abstracting frequently used `if (e instanceof MyErrorClass` is - as I've already said - a form of refactoring that makes handling custom error classes properly less of a chore. 5. Correct, nothing more to say about this.
There is a problem with catchError function: you don't actually flatten your code, because for each function call you have to add if-else statement. I use another solution: several custom errors extending Error. When I expect an error, I use try/catch and wrap an error inside my own error. Then it is being thrown up to presentation layer and transformed by custom filter, which applies logging and makes a client response based on error type and properties
They seem to be already doing that with a new safe assignment operator `?=`, which is a TC 39 proposal (or other syntax they're currently selecting, like `await try`). It basically does the same, if I got it right.
How come you ditch everything every week?
hes dutch
@@svens3722😂
@@svens3722 😂
Well he has to run a youtube channel 😅
This was also my reaction when I discovered there was this weird concept called "learning" that lets you find new strategies that you like better than your old ones.
I don’t buy it… you can just put try catch on separate sections … just keep it simple
Not only that you can have multiple catch clauses for a try clause.
Usually the controller is the best place to put try catch
@@caribouroadfarm You can? I just checked MDN and didn't find it, can you post a link? Or do you mean multiple if/else in a single catch block?
No dude, you're supposed to work differently to how the rest of the industry and your team works, you don't get it! /s
@@Kriszzzful I don't know if you can post links here, but those things called Conditional catch clauses just google it
we don't talk much about Effect, this lib is impressive
What do you use it for mostly if I may ask?
@@eprd313 Honestly, I'm not fully using it in my codebase yet, but you can watch Lucas Barake's videos with some cool examples
@@eprd313 I use it just for error handling on my Next.js app, it's cool i can catch specific error, i use it a lot on server actions
We’ve come full circle; ditching try/catch for .then().catch() 😆
That's what drove me crazy: async/await is syntax sugar that produces the exact same Promise. The error handling function could have easily been written as an async function with a try/catch block.
yeah, was thinking the same (that it could be rewritten to async try/catch) ... so what am I missing here?
Yeah it’s the exact same thing, but this creates reusable functionality. Otherwise you’d have to reimplement the same function for everything that handles promises.
I get "callback hell" vibe
@@dio56301 I doubt you're missing anything other than preferences. I can say, when I first started coding try {await x()} catch {} was easier to write and understand than .then(). That being said, this methodology in the video is fascinating and I will have to give it a try.
It's interesting for sure. Although I think all we've really done here is inverted the try-catch.
You're still handling the error at the top with if(error), and the else block is like the old "try"
We've also managed to escape the variable scoping of try-catch
And you can do an early exit on the error. One issue with try catch is that it inverts the exit early pattern that is very common place.
@@ojikutu Not really because he's basically just moved the scoping to the else block.
You wouldn't want to access the user variable outside the else block anyway
@@risitas5874 That's literally not how scoping works or what that means. Besides, as he also pointed out in the video, the only reason he is using the else block at all is because he was not inside of a function and could not early-return.
It’s fascinating to explore this possibilities, but I strongly recommend sticking to the try-catch approach 99% of the time, reserving the techniques discussed in this video for a few highly specific cases.
Why though? There a huge library building on this concept (EffectTS) and golang does this already by default. Also the inconvenience of try/catch is one of the two reasons why people still write:
data = await fetch(...)
result = await data.json()
This should throw a compile error and be red in your editor, but it isn't. JS/TS should know that this throws atleast 2 errors and expect you to handle them. There's even a TC39 proposal to add this as a new keyword (behaviour). While it doesn't solve the issue that the language doesnt know what could throw (EffectTS solved this as best as it could), its still very encouraging to actually handle errors properly with this. So i'm really interested in why you would not recommend it...
in my opinion this is not the best advice, because this is idiomatic with Go where it forces you at a langauge (& LSP) level to consume the error returned in some way but forcing this idom into typescript - which dont get me wrong i have done similar in the past as well - risks inconsistent implementation which means error states not interupting the control flow in the right way at the right times.
Forcing idioms doesn't always make sense even if they yield nicer syntax.
EDIT: Also worth thinking about the several ways that error propagation can 'escape' the promise chain depending on what async you're doing (not just promise based), whether its unawaited+not returned, using timeouts that aren't themselves promise wrapped and awaited etc, which would make your errors evade your generic catch block, ending up probably at the process level or at least far away from its lexical context. Then you're either doing a bunch of nested calls to your wrapper, or just doing some nested .catch or even worse relying on process.on() captures, or something else depending on context, which ends up with a messy mix of paradigms.
Imo it's just better to stay within the language idioms for predictability. Just my opinion though. If you want to write in languges that reliably return errors, and force you to consume those errors, use those languages. Typescript might have foibles in errors particularly in some async operations but this just seems like asking for trouble in production code.
In other words, idioms exist in langauges in the context of how that language operates. Just because you can replicate and 'paste over' 90% of the behaviour through functions in another language, does not mean that you should. That missing 10% is going to cause hella problems and if you work in a consequential domain then that's not really a good idea, just for the sake of some opinionated preference on syntax.
Just learn async really well, learn the event loop, handle it all idiomatically, and you're golden.
it's a different perspective, it makes it a little bit easier to write the flow of the logic without try catch, but you would need to handle the error flow yourself
@@amineabdz i think you're missing what im putting down
it's hard to read. try adding some line spaces.
@@jsvrs fair enough, have done so
I somewhat agree. It's still JS, and as long as you can enforce this pattern using a linter rule and CI job, any developer who isn't completely useless can learn the pattern intuitively in literally an hour.
This is how go handles errors by default, very pleasant
Yh it is, just that you return the error second and not first
@@chijiokejoseph7Lol if you’re using JavaScript, probably safe to assume you’re going to get a lot of errors at first
3:00 that isn't the only way to deal it it. You can use tools like "instanceof" in the catch to determine what type of error was thrown and deal with them in different ways just like you ended up doing it in a convoluted way.
Agreed. The only thing he's solving is not having to define the variable as a "let", using a "const" instead. But a const with extra type checking after is infinitely worse.
Besides, if you write proper functional code, you usually want to return the value at the end of your try block, in which case you can just assign the value to a const wherever you wanna call the function. So in reality, the situation he's trying to solve it not even a valid one.
The catchError funcion is exactly what golang does.
I tried to introduce that in a previous job and I got so much push back that I had I to remove all of it.
The only annoying thing is that, unless you use let, if you have multiple of those calls I the scope, you'll have to name the error const something different in every call.
Once a wise man said, "Don't bring one language's convention in another language"
Cause it's honestly stupid. No proper developer write try catch like that anyway. This is junior example.
it is *not* exactly what golang does.
@@chris94kennedy Sure, it's not native to the language, and golang doesn't return an array, it can return multiple values in the same funcion.
But in terms of flow, it's pretty much the same.
@@ericmackrodt9441 no - it is not. Please go and read my main comment on this video where i lay out why this is not the same - despite *looking and feeling the same in many/most cases* as golang.
"Ew, we have to move things into this try/catch. That's too much work!"
/proceeds to write a complicated generic function that mixes results with operation state
It's actually a very simple function. Here's a pure JavaScript version.
const catchError = promise => promise
.then(data => [undefined, data])
.catch(error => [error])
@@johnridout6540 Yes, im thinking of implementing this right now. I was quitely annoyed at the try catch situation i had before and this is a good solution.
Thanks for the video and great idea. I have to use a lot of trycatch in my job so this was really interesting. I would really like a video on Effect as it looks cool
If you have small functions which only do one thing, then you won't have a bunch of code tryign to interact with your user right there immediately after you fetch it. Your try catch can just be in the fetchUser function.
But then you have to somehow tell in the return value that what you returned is coming from an error. I know you can let this slip sometimes but this approach can't be used universally.
For example - say I have a method that gets something from the server and this something is an array of some items. Yes, you can always pass an empty array as a fallback but that does not say anything about the way of getting the data. What if you want to let user know that there's been an error getting the data and that's why they're seeing an empty list?
Sure, you can display the message in the try/capture block of this method but unless you have some kind of centralized error handling, most times this will not be the case.
An interesting approach but perhaps this is fixing smth that isn't broken?
This is no fix - it's an improvement over standard try/catch.
You can simply add add bracket for the let scope variable.
Try catch not meant to catch all error except the outermost one. You catch known errors and handle it accordingly.
If you comes from Java you know that's well how Try/Catch is suppose to work. The exception is try/catch need to carefully handle the asynchronous situation.
The correct solution is to try catch the fetch statement and return undefined if it fails instead of the user data.
What you're suggesting instead is a hacky version of a callback statement from the era before promises. We moved past that for a lot of good reasons. One of the problems you are going to face is if you do this twice in the same block of code and now have two variables named error. So now you're naming your errors things like error1 and error2? What is this recommendation.
Congratulations; you just reinvented the Golang much despised error-handling pattern! (which I happen to appreciate and prefer.)
And I mean congratulations sincerely, not sarcastically as try-catch used throughout a codebase is the root of all evil.
This doesn’t make sense. We have built ins for this. Use a try/catch and rethrow the error. You can provide a cause in the options if you want access to the original. In TS you can cast the error as unknown and you’ll be forced to check the type too. Sure abstract that into a function if you want something reusable but use the languages patterns and conventions
thank you for a sane comment. use the idioms that exist in the language because those idioms rely on the underlying mechanics to actually make sense. Just because you can get 80-90% of the way towards replicating a language behaviour from go in typescript does not mean you should, there are many ways this guy's code is going to go wrong. See my main comment for detail. It's sad to see a so-called senior engineer promote this imo because half the comments are like 'wow thats amazing im going to adopt this'. Enjoy the footguns.
So how would that abstracted function look like exactly? I am genuinely trying to understand. I understand you prefer a nested try catch over the Promise.then, but what is you return type exactly?
@@chris94kennedy a good idea will spread across languages until it gets native implementation. I am not familiar with Go syntax but I found this abstraction readable. Again you are complaining about footguns that already exist, whether you use this approach or not. What exactly would you suggest alternatively?
@@Kriszzzful i wrote a full separate comment on this video that makes my points so i dont feel the need to rewrite it and I'm not talking about syntax, in fact to the contrary ive said that that is the benefit of this approach but leads to issues. I have already made a suggestion so not sure why you're asking for it again, I said learn all the different forms and nuances of async in JS and how to write idiomatic error handling for all the edge cases.
Many great ideas that emerge after entrenched dogma appear not to make sense to the majority at first so the majority are very critical when first proposed.
But then more and more people start to consider that maybe the new approach is in-fact better than the previously assumed best practice. Over time, the majority go from being critics of the new approach to becoming advocates as the approach becomes mainstream.
I watched this happen with classic object-oriented programming vs. containment and delegation, and I expect to see the same happen over the coming years regarding exception handling (try-catch/throw) vs. errors-as-values. #fwiw
I prefer "withCatch" as name to tell it is a wrapper
Do you mean something like that?
function withCatch(fn, errorHandler) {
try {
const result = fn();
if (result instanceof Promise) {
return result.catch(errorHandler);
} else {
return result;
}
} catch (error) {
errorHandler(error);
}
}
Misunderstood at first. Disregard pls.
i prefer $catch
i prefer create a catch factory. we are not the same.
This is useful in an abstraction like your react-query fetcher that can give you the data or handle the error headers/body for you. For everything else try/catch is easier to read
Thanks for including the types! There's pros and cons to wrapping error handling in every request. I usually end up having a single try catch but the catch calls the application error handler with 10 or so Error types.
Effect also has an "effect/Micro" component that seems like a reasonable choice if we're mostly trying to get the benefits of the core Effect type and not all of the other powerful offerings that come with the full library. I'm still getting a feel for Effect since fp-ts announced the merger with it.
Hold up. fp-ts is merging with Effect?!
yep,i also use Effect/Micro and judt import the things i need like gen, try, tryPromise etc
I like this approach, already using it :)
as golang lover, this approach is the best, less tab more readable
Thank you Kyle. You're a good guy
Why do i need try catch nesting? Because error.message tells what is the problem with the code. Its not like it shows completely unreadable error.
Because some errors inside a try you can handle but others should throw? How would you achieve that in a single try catch?
I see try/catch as few critical unexpected errors and limited scope, known errors should be handled with if, userExists(), result is null etc. It feels wrong to overengineer or add more libraries into the picture when you can simplify instead.
I saw that Theo showed something similar and this comes from go which manages errors this way. You explained this really well and I will copy and adapt your code to my needs. Thanks 🙏
It all started with treating "not exceptional case" as exception. "User does not exist" is a regular workflow. Exception is when you can not connect to the server. From there it all went south. Some weird spaghetti code with promises instead of good old try/catch. Exception is designed to handle the error and exit the function. If you catch it and continue, something is fundamentally wrong.
Agreed. No point in continuing if user does not exists, though.
Very similar to the proposal for the safe-assignment operator, which has been modified to try-expressions instead. So we might get some syntax for something like this in the future.
Effect is awesome, more content about this would be terrific
lad has reinvented golang error handling
Promise.try will officially will be out soon! It's pretty amazing!
Try catch is a low level pattern and can be used perfectly for catching errors. But, you have to type your errors.
In that case you get multiple catches for every error type or an if statement in your catch to check the error type.
I just did command + s while watching your code 😁
It's a good advice, it's work make it simple... Declaring try/catch block many times in the same function sometimes is boring!
Basically it is a Result pattern in javascript. Great video by the way! I''ve been using something like this for a while!
Or std::optional in C++ that was from Alexandrescu's 2001 book that became the basis for Result. But I think this is less good than either, and more like Go's error handling, but also worse than that. Interesting, and possibly useful. More like a change of pace than any real benefits.
"try and catch" role is to catch Exception, "unexpected behavior" like network loss, i/o problems, etc.. Not for typo or error made by developer.
In PHP, if you try to print_r() a undefined variable, it will raise an Warning error. In Java, because of compilation, your code would not even compile at all because to try to do something with undefined variable.
In JS, in your example, it will failed because your variable is defined in the scope of the "try and catch" ... again, it's a language feature ...
So, the problem here are about the pro and cons of how scopes are generally handled on the language level (just compare php/js/python per example).
Until TypeScript become a real language on its own, you'll have live with the fact that is JavaScript is behind executing it ...
But, I love it for what it offerts :)
fantastic, i love this. I learned something new
Your filming approach suggests a thorough understanding of the subject matter.
The motivation in this video is to have specific error handling depending on which of the called functions failed.
We do this in the big common catch block by testing the error class (if necessary). Writing log output is already done in the called functions.
The only distinction on caller side is whether to retry or whether to continue with a warning vs. rethrow the exception.
This is not an issue with try catch, it's a problem with how you think about architecture, usually no one would store a global user outside their function and set it's value later, you could've literally had the try catch inside the getUser function and return a user from the get user function and do any other error handling in the catch block, when you're writing actual business logic, that repository code will obviously be talking to an external service and "returning" something, not setting a global variable, then some other function in your application logic will call the repository and the getUser will return a user type, if there's any specific error handling try catch will still do the same thing, what you did was to write boilerplate for wrapping then catch
This is like one of those infomercials in where clumsy people dont know how to use certain appliance or device, and then comes the savior with a tries to solve a problem that no-one has
Thanks!
Thanks for the support!
I fell in love with this approach in Go. So glad this is being adopted into the Typescript ecosystem. It makes so much sense.
I like the Rust approach better, just return Result | Error, and then you have to narrow away the Error type to continue using the Result value.
@@Gakai08 interesting. I have never used Rust, but I’ve heard this same point from many others. I’ll take a look.
@@Gakai08yes, error handling *feels* better in rust when compared to go. But both the languages embrace errors as values approach, so it's not that much different, just different syntax or pattern.
@@legends_assemble4938 Exactly
it goes like this Haskell/Ocaml features -> Rust and Scala adopt them -> more mainstream and normie languages Js, go, pyton adopt them.
Supabase's API works kinda like that. I dont dislike it, but it can get messy with multiple promises with multiple arrays with errors. I guess the best solution depends on your project.
Yes please, would love a tutorial on Effect.
After working in go for a bit, I've come to like the multiple return values with the second value being an optional error. Just check if the error was returned and handle it there. if you return a value regardless of an error, it makes the code even safer.
Very clean
It's the same way the supabase JS SDK handles this issue. They return an object not array but otherwise it's the exact same idea! Great to see how it actually works for anything you want it to, rather than a specific SDK!!!
well that's it, I'm adding this to my python code. I need that since I have raise error in multiple places :) thanks!
This is solid.
Effect tutorial would be great! 🎉
Yet another one - try few nested calls... Welcome back to try/catch
Nice, similar idea of error as a value is a built-in feature in Golang :)
good idea!
Doug's Coding Corner posted a video exactly about this 2 days ago.
This is a long standing debate. Errors as throws or errors as values. If you want to adopt errors as values in javascript, you definitely can. Golang, instead of re-throwing, just bubbles the error, returning it.
You can use this at the top level of your throw-rethrow stack, where you will not rethrow the error but handle it. I personally love the errors as values, but everything in Javascript ecosystem is throw based, so it will be a little bit hard.
Of course if we get the ?= operator, then things could change.
Forget all the whiners, this great. I hate dealing with the scoping issues that come with try-catch, and I hate that in typescript, the error is basically untyped which, in fairness, it may actually be something you can't actually predict, but I'd rather know if a function call can throw or not, and kinds of errors it can throw.
Cool, but maybe a nice usability addition would be to have a result type (object). Returning an array will inevitably pollute the variable space if you need to use the catch function several times, you will end up with err,err2,err3, etc. You could also declare the results as variables instead of using const, do I don’t know if that is a thing
Or you could use meaningful variable names. Instead of the incredibly useless err, name it userFetchError.
You have GOT to be the closest thing humanity has to the perfect video creator. Clear, concise speech, beyond perfect presentation, fully planned and prepared lessons, and material so well written that it's mind-boggling. As if that weren't enough, there is also unparalleled depth of knowledge and understanding. A video of yours is the de facto standard for all others. NOBODY matches the quality of your videos on every conceivable front.
I've been working in web dev for a few months and I never had a problem with this kind of thing. Though we defined a similar function (tryCatch, which literally tries and catches the callback you pass to it) just to have a more modular code
Interrested by Effect.
Practically there should be error handling with try/catch in the getUser function to begin with and that would have solved all the problem you mentioned.
We would love a full Effect tutorial. We are thinking of adopting it!
To the ppl saying this would lead to having multiple constants named error, we could return either an Error or a result instead of an array, and verify if result instanceof Error or not
If you use try catch and throw new Error.
You can also use console.log(error.message ?? error) in catch block.
This approach looks similar to how Go handles errors
Yeah, I was thinking the same...
Where do you think he got the idea from? Just another excuse to make a 10 minute video and collect more views.
@@adtc It doesn't really take a genius to discover this - I've been using this for years already. I do agree - lately I've been seeing more videos about this subject. It seems to be easily milkable so I guess it's because of that. But hey, it's a nice pattern so spreading the word is beneficial.
@@Jajoo13 A nice pattern if you like wasting time and money 😂
@@zakarylittle6767 ?
If you’re in a react project then react-query is top notch and has this functionality and much more built into hooks
Yeah No. This trend of ditching try-catch is funny.
I remember the good old days when errors by value was big no no.
What happened to keeping business logic separate from error management ?
The main reason I don't like Go. No try-catch.
the golang way, very nice
😆 LoL, 🤦♂️ what a funny guy.
The main reason try-catch sucks is when it's frivolously used to mitigate foul logical constructs, otherwise it's very necessary for errors that occur beyond your control (mostly external input).
It's a common pattern among data fetching libraries. const {loading,data,error}=useQuery(...
At 1:52 it’s slightly false.
You can define a `user` outside try, then affect inside, then exit main path on the catch block, while keeping main path intact. 😊
(Edit: Talked about at 3:02, nice.)
I've always wished languages had something like a data type of error or a class of error that had the special property that if you test against it, it always returns false. So a function call would return the normal value, or the error. And you just need to test against the return value to see if it worked. In cases of errors, the object would have properties for the error message, a stack trace, etc.
It's just a basic abstraction. I always use something similar to wrap my error sensitive block. It makes a lot of sense in middleware to just pass the error to a handler afterward. If not, it is just adding more control flow.
If I remember correctly, throws or exceptions inside of an async function or context might not actually fire the catch in the calling function.
There are other even subtler tricks that a real developer would know about, but typeshit will never let you even get near those edge cases, it is by idiots for idiots, playground guardrails.
@@adonisengineering5508 Let me guess, you code in Vim?
I vote for Effect tutor video👍
isn't it essentially the same as putting a try catch in a separate function instead of writing the logic directly in the current function though ? I mean, I like that it make the original function more readable but you can write the very same logic with your withCatch function being async and using a try/catch/await inside just like in your original function and simply return the same thing you returned in your promise. The point is what your approach didn't really change anything about the usefulness of a try and catch, it's just moved the logic on a separate function but try and catch can be use in that separate function just the same.
async function catchError(promise: Promise): Promise {
try {
return [undefined, await promise]
} catch (error) {
return [error]
}
}
const [error, user] = await catchError(getUser(1))
It feels very golangy and I love it.
reminds me of effect error handling (from the functional effect ecosystem of tools, not the react hook)
Edit: now that i watched till the end, I can see you mentioned it too 😂
The video is well-explained, but the approach really depends on the nature of the task. For simpler tasks with fewer sections, using a basic try-catch in each section can be an effective and straightforward solution. However, in extremely complex situations, using catchErrorTyped with await works very well, as it provides more control and clarity in error handling. It’s about using the right tool for the job: just as you’d use a knife to cut vegetables, not a sword, a simple try-catch can be more suitable for simpler needs.
This video should actually being titled "I don't understand how to use Try/Catch properly"
'satisfies [undefined, T]' looks sexier than 'as [undefined, T]' :)
catch(error) there for a reason.
You can get an idea about the error once you read the error.
Personally if the function is one of my own - like a get_user() abstraction of a database call - I like to return errors in the form of an object like `{ error: Error_Enum; data: T }`. To handle exceptions the ORM or any other applicable external function calls might throw I definitely reach for a catch_error() style.
It's go syntax.
You can have several catches in a try code block and throw your exceptions by treating each case separately.
The reason you gave for not using a try catch doesn't make sense at all. While using try catch and logging the error you can print the actual error message by console logging the error plus your custom message. eg catch { console.log('Custom error message' : error)}. Your method is very obscure, I thought you were supposed to simplify the web for us, Kyle.
Every time I see this it reminds me of this funny Go joke:
Go always keeps two cups by the bedside. One cup has water, while the other is always empty.
Rust asks puzzled: "Why do you always keep two cups here?"
Go replies: "Because I need to drink water when I wake up at night."
Rust follows up: "But why keep an empty cup?"
Go: "What if one day I wake up and don't want to drink water?"
Thanks for helping me understand try{...}catch{...} better. :P
I'm not sure if I will use this solution. I don't quite understand advanced fix yet but the basic fix is interesting, and it's reusable.
It's not a good solution in my opinion. Read other comments feedback on this same video. 😅
@@arvi8843 I'm not an expert at programming, i'm probably wrong. But you think it's a bad solution simply because most people say so?
@@ring11037 No. I'm just lazy at writing my explanation so I told you to read what other people say so. 🤣 Personally, I don't want my function calls wrapped around something like catchX(myFunction). The try/catch is enough if you know where to put it. Routers also have a "next" step func and catch all error handling. What he's showing here is just snippet of code not the actual arch design of the app.
Thanks for the great video. I'll be interested in the video about the Effect library, please
Interesting. Can you explain each part of the function declaration in the catchErrorTyped function. Some of it, I'm not sure I understand.
This is why I prefer Rust! I can't wait until the day that I can use Rust for the front end of enterprise web applications without compromise.
Might as well use Effect
Now you are being stupid 😂😂😂
+1 I would love to learn more about Effect lib.
So re-inventented tanstack query wrapper for async calls?
Despite the comments. Things are moving in this direction. Plaid TS SDK returns { data, error } everywhere. They never throw errors and so you never need to catch. This is the way. Is this like Go? Yes, because Go is superior. However, when you can't use Go there is this approach.
Sure it's a better experience, but the language doesn't force it so almost no one will do this. New projects using this will eventually get developers who ruin the pattern, and now you're left with some places where errors are handled like this, some places with try/catch, and places where error catching doesn't happen at all
This needs to be part of a library to be useful(I agree, effect is too bloated if you just want this), and there needs to be strict linter rules to prevent people from doing it any other way.
1. If your code blocks inside `try` are too long, then your code is doing too much. Refactor.
2. The `Error` class can be easily subclassed, so you can throw application specific error types
3. With subclassed errors you can easily do `if (error instanceof MyErrorClass)` and then rethrow the error if it doesn't match
4. In the case of TypeScript, your `usr` syntax error will get caught by the compiler. This is more applicable to pure JS.
5. Even in JS, your model won't catch a syntax error *in the called function* - it'll still throw that generic error.
1. He did refactor
2. He also did that
3. He also did that, and abstracted it nicely
4. Syntax errors were used as an example. Additionally, TSC is not actually a compiler and is far from perfect. Unexpected runtime errors are very real and abstracting frequently used `if (e instanceof MyErrorClass` is - as I've already said - a form of refactoring that makes handling custom error classes properly less of a chore.
5. Correct, nothing more to say about this.
There is a problem with catchError function: you don't actually flatten your code, because for each function call you have to add if-else statement. I use another solution: several custom errors extending Error. When I expect an error, I use try/catch and wrap an error inside my own error. Then it is being thrown up to presentation layer and transformed by custom filter, which applies logging and makes a client response based on error type and properties
JS should just incorporate a new "try/catch" that doesn't create a new scope. They don't even have to break the old version.
They seem to be already doing that with a new safe assignment operator `?=`, which is a TC 39 proposal (or other syntax they're currently selecting, like `await try`). It basically does the same, if I got it right.
He is the best programmer