Try scoping makes perfect sense. You're assuming that your catch block returns or throws. If it doesn't, then there's no guarantee that a value assigned in a try block isn't uninitialized. If your catch block didn't return in the first example, then your function would throw a TypeError because you can't multiply undefined by 2. Thus that line _can_ throw, and so belongs in the try block. Then you can look at the return statement. Unless you want your function to be able to return undefined, then you have an error in your logic. To fix the logic, move the return statement into the try block with its dependencies so that it doesn't run if its dependencies didn't resolve.
@masterflitzer4 ай бұрын
that's why errors as values are just indefinitely better than exceptions and try/catch also if you've watched to the end he discovered that and recognized the scoping makes sense how it is
@dinhero214 ай бұрын
JavaScript can't type error in multiplications, the only way you can error in a multiplication is if one of the factors's prototype's valueOf function throws in the video's case, undefined * 2 is NaN
@gabrielpeleskei30754 ай бұрын
Scoping is necessary and correct. It is plain skill issues. The calculation based on the throwable function must be in the same scope. Putting it outside makes no sense. The design to return errors or throw them is equaliy good or bad. The problem in Javascript is the handling of the function definition (you may not know that a function throws). It could be changed inspired by swift, needing to add a throws section to the function definition if a function actually uses the throw statement. This example tries using try-catch in errors-as-value manner. Just don't!
@masterflitzer4 ай бұрын
@@gabrielpeleskei3075 isn't that exactly what the comment said: "try scoping makes perfect sense"? what additional info does your comment give?
@gabrielpeleskei30754 ай бұрын
@@masterflitzer If you don't see any, that's good with me.
@the-old-channel4 ай бұрын
The real solution to this is to make so that blocks are expressions. So we can do: let double = try { maybeNumber() * 2 } catch { -1 } Rust got it right
@vvvvvvvvvwv4 ай бұрын
Yes, or errors should be values
@dzigizord65674 ай бұрын
this is the way
@DanKaschel4 ай бұрын
Why is rust so good at everything 😭
@lengors73273 ай бұрын
Was gonna say the same thing, but using Kotlin as an example.
@DASPRiDАй бұрын
Yep, in Rust everything just falls into place, it's just so well designed :D
@christopher-pfeifer4 ай бұрын
Theo must have something against pirates, pronouncing "var" like "vair."
@kettenbach4 ай бұрын
verror, vare, vair . I don't car ☝️😃
@ccash32904 ай бұрын
I think it's because var is from variable. I still hate it though
@567legodude4 ай бұрын
@@ccash3290 and that's why we should all say car as in carriage
4 ай бұрын
@@567legodude I agree with your point and unfortunately that means we both need to pronounce "gif" as "jiff" now
@Bittboy4 ай бұрын
Tomato tomato, potato potato 🤷 just the weigh she goes
@shaheennamboori4 ай бұрын
6:20 Writes a custom error "Forcing var to never get hit" -> Sees the error in the logs -> Interesting that they have "Forcing var to never get hit" error 😂😂😂
@testaccountyt3 ай бұрын
I thought the same thing, Theo has the memory of a goldfish
@comradepeter874 ай бұрын
My 5 year old son instantly said "Lest some scoped variables could be left in an invalid/indeterminate state!". He's so smart.
@05xpeter4 ай бұрын
After my daughter's first day of school. She came home and declared "zero is the first number". I was happy she was on the right team. A week later she asked: "Dad are you sure that if you count long enough you will not get back to zero". I said "yes I'm sure". She wasn't buying it and asked me multiple times. At that point I tried to settle with fact that my daughter probably would become a C-developer.
@milanstevic84244 ай бұрын
@@05xpeter In fact you are not sure. There are at least half as many algebras in which counting upward would get one back to zero. This is equally real and possible in mathematics as the infinity you're indoctrinated with. Numbers aren't a physical thing, everything is a convention, and cyclic number systems are arguably even more rigorously defined than infinite sets, especially when it comes to integers.
@05xpeter4 ай бұрын
@@milanstevic8424 Yeah there is always the possibility that see could become a mathematician. I guess I though about that when I named her "Tilde", my wife wan't happy when she found out that I had put a math easter egg in her name. But as I told her it had to be "Tilde" I couldn't call her "Line" (that's my wife's name) and "Cirkle" is a little to much.
@Georgggg4 ай бұрын
try scope is annoying, but not a big deal.
@567legodude4 ай бұрын
It would be a strange break of consistency to have one particular block not be scoped. I'm looking at this from the perspective of C-like languages, where just using plain brackets alone serves no other purpose than to introduce scope.
@balduran4 ай бұрын
You can use brackets on their own in javascript too. But I never had a situation where this made sense to me.
@kered134 ай бұрын
@@balduran It can make sense sometimes in C++ (probably Rust too) by allowing you to use block scope to control the lifetime of objects using RAII. In languages without RAII there is no reason to use it.
@asailijhijr3 ай бұрын
@@balduran I've tried to do it a couple of times, but it threw errors so I gave up, I'll have to revisit it sometime.
@tomnussbaumer4 ай бұрын
An additional argument is syntax: when something looks like a block (surrounded by the curly brackets) why shouldn't it be a block? Any language I've used in my decades of coding handles it this way and it makes perfect sense.
@MrTomyCJ4 ай бұрын
The idea would be changing the syntax along the scoping behavior, to prevent that consistency issue.
@JeyPeyy4 ай бұрын
Object literals use curly braces, but they're not a block per se. But I guess it's a bit different when it's an expression rather than a statement, which is usually easy to distinguish
@JoRyGu4 ай бұрын
Theo: "var hoisting behavior is bad" Also Theo: "we should be able to hoist variables outside of try blocks" Makes sense.
@ninocraft14 ай бұрын
js mind virus take from theo again xD
@386enhanced4 ай бұрын
@@ninocraft1 isn't he a React shill
@akam99194 ай бұрын
In his defense, I would say that hoisting within a try block is very different than from within a function or a loop.
@JoRyGu4 ай бұрын
@@akam9919 Except that it's completely inconsistent with every other block scoping behavior in JS. Why would you make one single exception? That is setting yourself up for disaster.
@chris72634 ай бұрын
As someone in the process of learning js, the fact that var hoists *differently* is what makes me feel a little hysterical.
@Z3rgatul4 ай бұрын
javascript devs discover in 2024 why try/catch statements create separate scope 🤣🤣🤣 it was designed in this way for Java and C++ for a very good reason. And most likely there was some small unknown language that had the same concept 10+ years before Java was even created
@xenitane4 ай бұрын
my exact thoughts, like dude that's not a js issue, it's an oop design thing. and as error handling goes, i'm loving how go/rust/zig treat errors as values, much pleasant to work with.
@warrenarnoldmusic4 ай бұрын
Js devs choose random hills to die on😅
@Bu7MaiD0754 ай бұрын
The reason is memory. It always comes down memory.
@PraiseYeezus4 ай бұрын
@@xenitane this has next to nothing to do with oop design lol block scoping (with brackets) comes from C, same place Java and C++ got it from.
@PraiseYeezus4 ай бұрын
are you even following the discussion? it's more so about function scope vs. block scope, not scope in general. considering JS has embraced both design paradigms at some point it's not crazy to see some people weigh the pros/cons of both
@jakeleventhal41754 ай бұрын
blocked scope is the correct implementation. NNN
@adnan76984 ай бұрын
No nut November?
@FurryDanOriginal4 ай бұрын
@@ononaokisama No Nut November
@Daneus_GMD4 ай бұрын
bro wrote a community note on a yt video
@brennan1234 ай бұрын
I've liked Option/Result type of error handling. Especially with exhaustive pattern matching languages. Code is much more transparent on what it can return, increased type safety, and compiler can reason deeper about your code and catch more errors at compile time. Also gives you the lower level primitives to build custom higher level error handling more suited to your particular use case. Having to use `let` outside of the try/catch as a workaround and not being able to use `const` just feels dirty and unnecessarily imperative to me.
@fjjfjfj4 ай бұрын
I feel like we would all be better programmers if we are forced to deal with error cases, enforced by the types, more often. It would be annoying, and maybe less efficient, but hopefully less breaking stuff :)
@brennan1234 ай бұрын
@@fjjfjfj Performance is an interesting point. There are probably scenarios where checks can be skipped. There's also scenarios where things like bounds checking of arrays don't need to be performed because the compiler can guarantee it's not empty. There's even ways to create types for non-empty arrays as well. I don't think we're there yet, but I've always wondering if compiler technology improves and we can specify our intent at higher levels, the compilers can make more assumptions about the code and optimize better. Really hard to do with imperative / procedural code though.
@TQuantP3 ай бұрын
That'd one extra reason to love golang's errors as values implementation... Everything is so crystal clear because of that! (that and the fact that Go has even less syntactic sugar than C 🤣)
4 ай бұрын
Watch to the end - Theo realizes the scoping in `try {}` is needed after all! I hope Theo pins a comment about this. The problem: while declarations are hoisted in blocks, initializations are not. An error within a try can result in an initialization not being reached; therefore it is control flow.
@sorrynotsorry82244 ай бұрын
This is why I've gotten into the habit of moving try-catch blocks into their own functions and use something like Go's pattern (which I really like).
@MrManafon4 ай бұрын
This is the correct answer. I took the same pattern from Elixir, where try blocks have a return and their result can be assigned, making them effectively tiny functions with syntax sugar.
@pranav_bhasin4 ай бұрын
This is exactly where I prefer the approach that Kotlin takes. It maintains the scoping but allows returns from the evaluated value of the try/catch flow. It would look a bit like the following in JS: const num = try { getNumber(); } catch (e) { console.log(e); return -1 }
@natrixnatrix4 ай бұрын
The fact that a substantial number of people think that curly braces should sometimes create a new scope and sometimes not is honestly kind of insane to me. The reason var works is not because it hoists but because of inconsistent scoping rules like this. The hoisting part has nothing to do with.
@PedroPedruzzi4 ай бұрын
The main shortcoming of the try catch syntax is that it isn't an expression. In practice it forces the pattern of isolating the block in another function so that one can return something useful in both try and catch blocks. e.g. try { return getUnsafeStuff() } catch (err) { return getAlternativeStuff() } A new expression syntax would help. But I've people using a helper like this: const result = orElse(() => getUnsafeStuff(), err => getAlternativeStuff())
@2547techno4 ай бұрын
12 minutes of straight yap
@utilyre4 ай бұрын
This is why people like errors as values. It's not that returning an error is much different than throwing it, but the fact that you only scope the error case outside of the function is what people like.
@vsolyomi4 ай бұрын
"What's control flow?.." - Theo, Aug 2024 :)
@stephenbelanger4 ай бұрын
If the block scope of the function leaks out of the try then throwing midway through would result in uninitialized variables. With using let outside and reassigning you have an explicit initialization which can be set to a new value if the try succeeds. If you hoist the assignments out you either need var semantics where everything is initialized to undefined at the start of the function and then changed wherever the var statement occurs. Uninitialized state is very unsafe, especially with block-scoped variables as they are initialized at statement position not hoisted to the top of the block, so variable initialization might not be reached and then subsequent code which assumed initialization happened could corrupt memory and crash the process. Unscoped try/catch wouldn’t work the way you think it should.
@ValaSelene4 ай бұрын
I think it's also worth covering that it could also break on the line that does the assignment, but still *before* assignment, depending on what it does. If `const foo = MightThrow()` then `MightThrow()` throwing would occur before assignment as function execution takes place before assignment. I.e. don't think of it as every separate line potentially failing so much as every separate operation within every line.
@Bu7MaiD0754 ай бұрын
javascript devs are too comfortable with changing javascript than themselves
@warrenarnoldmusic4 ай бұрын
A bug: skill issue Js devs: it must be the language, no it is the browser, no it is the device 💀😅
@DarthVader119124 ай бұрын
@@warrenarnoldmusic Nah, it's mostly the rust devs who try out javascript and don't understand a thing.
@mugiseyebrows4 ай бұрын
Hot take: that's how things probably should be, any part of computer system should be able to evolve through change (except for null being an object, obviously, that should stay forever this way). Javascript was mediocre before it become good.
@danielt88803 ай бұрын
@DarthVader11912 yeah right? blame it on rust dev
@theaninova4 ай бұрын
I don't know why this wasn't brought up, but the solution is absolutely not to have the variable be hoisted, the solution is to have try (and if and other control flow) as an expression. const num = try { getNumber() } catch (e) { return -1; }; const double = num * 2; Rust does this and it's excellent. I don't even think this would interfere with the current JS grammar since try as an expression is illegal, and the return of the last value of a try statement is a noop so equivalent to the old behaviour.
@collinoly4 ай бұрын
I wish typescript could tell me if something can throw. The amount of things you don’t know can throw is so hard to deal with
@migueljara93994 ай бұрын
This is actually something I've searched about as I thought the very same thing as you. And luckily, Anders Hejlsberg (C# & TS creator) has talked about it. His opinion was that although beneficial for your code, integrating this with 3rd party libraries would be really bad DX as you would have to handle a bunch of cases that you may not even be interested about from these libraries. Of course his opinion was much deeper than that, but that's the overall opinion I got from him. I don't wanna link anything here as I don't know how youtube treats comments with links (bots are allowed but actual people not, huh?). But you can look it up as "The Trouble with Checked Exceptions A Conversation with Anders Hejlsberg, Part II" it's an article from "Artima" where they interview uncle Anders. It's a good and recommended 5min read :)
@panmateusz4 ай бұрын
I wished that once too, and then I tried java which statically analyses exceptions. It's very annoying to deal with. I think its just better to have one catch-all statement to handle all potential runtime exceptions. (But if if it was an optional feature of typescript I would still like it for some use cases)
@webcodr4 ай бұрын
@@panmateusz It's a little more complicated in Java. Initially Java used so-called checked exceptions and handling was mandatory. Either by try/catch or using the 'throws' keyword after a method signature with all exceptions this method could throw. Well, that decision didn't age very well and runtime exceptions were introduced. They work basically like JS errors. Older parts of the stdlib and some libraries still use checked exceptions, more modern parts and most libraries use runtime exceptions. This is also very annoying. I'm really thankful that JetBrains dropped checked exceptions in Kotlin completly. There's an annotation available instead of the keyword 'throws', but it's not mandatory to use it. But Kotlin allows you to not use exceptions at all. Thanks to sealed classes and some other language features, it's very easy to implement a result object for handling success and error states (Kotlin has a native result class, but IMO it's not that great, so I wrote my own). It's not as convenient as with Rust, but it works fine, makes error handling easier and forces you to handle errors. It's a little verbose at times, sometimes annoying, but in the end, a way better error handling with less potential for bugs. And at least for my taste, nicer than Go's approach will tuples and the omni-present null-checks of err.
@kered134 ай бұрын
You should just assume that every non-trivial function can throw (this is usually true).
@Goshified4 ай бұрын
I'm not sure why this wasn't mentioned, but `finally` helps a lot when writing `try/catch` in JS. There are a lot of control flow issues that you can solve there. It allows you to write some code after that will execute even if the failure happens.
@captain.america11234 ай бұрын
It's a bad idea to have control flow statements inside the `finally` block. It should only be used for cleanup code, ex: Closing files, DBs etc.
@Goshified4 ай бұрын
I definitely agree that it’s not something that should be used _all_ of the time. In retrospect, my original comment shouldn’t have called out the “control flow” piece specifically. I only meant to draw attention to the third part of “try/catch” that isn’t discussed much which can be used to solve _some_ issues and help with writing cleaner code.
@3ventic4 ай бұрын
I just pull those kinds of short try-catch variable definitions into their own function and suddenly it's no longer a problem. It can even be a local function within the function that needs to do it.
@darylphuah4 ай бұрын
Great to see you learnt why its a good idea to scope the try block.
@3lH4ck3rC0mf0r74 ай бұрын
I mean, anything that might depend on unreliable (read: may fail/error) code is also, by definition, unreliable. A chain is only a strong as its weakest link. Therefore, it makes more sense to put the lines that may throw, and everything else that depends on it inside the try block, especially since in this simple case it just conditionally doubles the value and returns it with no extra dependencies. This solves the problem of trying to access an out-of-scope variable, and also keeps the code better organized, and easier to read and maintain. Keep in mind, I/O failure is also a thing. Your file may succesfully open, but that doesn't necessarily mean it'll succesfully read, or write. Might as well group all operations involving the same unreliable foreign resource into a single try-catch block. Also, var hoisting is a mistake and should be violently ignored moving forward. It's only kept around for backwards compatibility with old, broken code. Please, just pretend it doesn't exist. The only circumstance one could possibly prefer var hoisting is to use it instead of the finally block, to check-not-undefined and then free resources. But it's ridiculous, no sane person would prefer to declare, but not define a variable at the top of the scope, IMPLICITLY. And if you think you found a legitimate usecase, please just study more. There's a reason why this behavior does not exist in other languages, and if you want to declare, but not define a variable at the top of the scope, you have to do so explicitly. If it wasn't because mistakes were made and old JS must still continue working, let would've replaced var.
@BuyMyBeard4 ай бұрын
Another reason to love Rust with match assignment destructuring and Result monad. Dealing with failable code while assigning something is a piece of cake.
@SG_014 ай бұрын
With a lot of people doggedly holding on to their various opinions, it's honestly good to see someone change their opinion while working through something. Honestly based.
@paxdriver4 ай бұрын
@6:23 Theo finds his own error message interesting 😂 Love these javascript vids. Catharsis all wrapped in a senseless block. You should do one on defining objects with variables for keys. That syntax is so confusing where the key can be dropped or wrapped in brackets, or spread, but only shallowly spread... And there's no built-in optimised deep copy! I have to trust a util or write a case for every symbol, collection, set, array, etc, etc if I plan to reuse it lol
@lukasgerhold78224 ай бұрын
structuredClone() is a built-in deep-clone function if I understood your problem correctly
@drizzly_dips4 ай бұрын
@paxdriver lodash solves deep copy (and a bunch of other utilities that should be be built in). big time saver.
@Tyheir4 ай бұрын
If err != nil
@theairaccumulator71444 ай бұрын
worse
@vinialves123624 ай бұрын
@@theairaccumulator7144 you are worse, this is better
@steadexe4 ай бұрын
@@theairaccumulator7144 I don't think it's worse, atleast it doesn't indent the code for no reason. But I agree it might get more chatty if you need to handle several errors.
@paca31074 ай бұрын
its annoying but useful
@CottidaeSEA4 ай бұрын
@@steadexe You should place as little as possible inside your try-catch. If your entire code is wrapped in a try-catch then that's just a skill issue. The indentation should be pretty much the same as what "if err != nil" causes. In fact, wrapping as little as possible in try-catch is preferable since if you have a lot of your code wrapped in it, you might catch unexpected and unintended errors which could be problematic in many ways. You also want to handle errors as quickly as possible and restore to a normal state, or exit with an expected failure state rather than an unknown one.
@warrenarnoldmusic4 ай бұрын
A bug: skill issue Js devs: it must be the language, no it is the browser, no it is the device 💀😅
@chriss34044 ай бұрын
it's always the hardware's fault!
@shaunmodipane14 ай бұрын
3:07 I don't get what is wrong with having all your code inside the try block.
@shaunmodipane14 ай бұрын
and what is wrong with if-else to handle errors or "unexpected"/not happy paths conditions
@Pete1334 ай бұрын
Yeah I don’t see why it’s bad either. If you want to handle errors from the other function call then why not put a separate try/catch in that other function?
@kered134 ай бұрын
The problem it separates the error handling from the point where the error arises. As the function becomes longer, this problem grows. The solution is typically to factor out the part of the function that can fail into a new function that includes the error handling.
@matthewchampagne96214 ай бұрын
The problem is that hoisting shouldn't be an option. That makes no sense. Scopes should always be real and have no edge cases but that's JS for you.
@tinytim424 ай бұрын
We have to choose between consistent block scopes or marginally better ergonomics here. I’m happy the authors chose consistency.
@alexandr0id4 ай бұрын
Try catch behaves like this in all Java-like languages. The whole try-catch pattern is rotten and it's not JavaScript's fault.
@dazraf4 ай бұрын
i'm not functional programming this is a topic as old as time. basically an Either monad that can be passed from one function to the next.
@velitasali4 ай бұрын
Bruh 10:05, if you know try block is not going to fail then keep things in the try block, wth on about there. Also it is not that try block is a control flow in and of itself but anything is a scope when you put them inside {}. You can create scope with {} without using any keyword, you know that right?
@erikbrendel32174 ай бұрын
Nice! Thanks for openly showing that changing your mind after learning something is a good thing :D
@bagofmanytricks3 ай бұрын
In JS you need to think of try-catch as something that protects a whole scope, like the whole function. Happy path should happen in try{} and Failure path should happen in catch{}. If something always needs to happen last it must happen in finally{}
@bloody_albatross4 ай бұрын
In Python a try block can have an else block, which only runs if there was no exception thrown. With proper scoping that would make even more sense than in Python. So you could do: try { const x = foo(); } catch (e) { // cannot use x } else { bar(x); } finally { // cannot use x } // cannot use x
@pyrouscomments4 ай бұрын
I kinda see what you mean, but I'd argue this makes even less sense in Python and is in fact one of the reasons that: 1 - variables are not declared; 2 - not every block suite creates a new scope. What I mean is that the confusion on what's defined were in the control flow in general is something to avoid as a design principle, which leads to the above two points. Anyway, I keep trying to give JS a chance, but...
@Lambda_Ovine4 ай бұрын
aaannnd this is why i kinda dislike python... an esoteric block of code that can only run if there was no exception and conceptually merges if and try blocks? shouldn't the code that only runs if an exception wasn't thrown be just the rest of the function? just pre-set your variables outside the scope when it applies people, is not the end of the world
@bloody_albatross4 ай бұрын
Another solution would be a try expression where the try block can return a value (or return from the function). Then you can declare the variable to be set as const.
@ValerianAndStuff4 ай бұрын
what the fuck
@Мартынов-х3ъ4 ай бұрын
@@Lambda_Ovineesoteric block of code is kinda funny ngl. There are else statements for both for and while loops in python though, so having it for try is predictable
@jaye56324 ай бұрын
This makes sense if you take the return out of the catch. Anything which is then hoisted could be an error outside of the try catch. Accessing the variable again would mean that you are now referencing the error object
@DNAMIX14 ай бұрын
Just put each and every potentially undefined handle (database connection or rather a grab from some connection pool etc.) inside dedicated and properly scoped semantic codeblock, no?
@SirCorrino4 ай бұрын
I like it the way they do it in Kotlin. Where you can return a value from the try/catch and assign that to the variable you want.
@hammerbot8384 ай бұрын
lol I loved the change of mind when you figure it out. Honestly, you can get away with a lot of things mixing the `try/catch` syntax and the `.catch()` syntax when necessary and it's really ok. What bothers me the most with exceptions is that they are not typed. In java, we declare a function saying what the function can throw so the catch block is also typed. This is not the case with Typescript and I believe it sucks
@atlasxatlas4 ай бұрын
i love the realization mid vid, and acceptance and opinion change. well done
@rubenchiquin37684 ай бұрын
There's other languages that handle this in a very unique way, that I think would be doable in typescript. For example Scala's Try Basically it's like a try catch block, but without catch. The catch happens in the type definition. So any exception thrown is saved in the return type. The simplest example is a function to convert a string to number: def parseInt(s: String): Try[Int] = Try(s.toInt) Now the result of the function parseInt is of type Try, which already tells you (much more explicitly than the ghostly typescript errors) that this number that you get returned is really inside this Try wrapper which can fail, which forces you to deal with the error before accessing it, much like Go with their err != nil spam. Just that in Scala, you can decide when to unwrap and deal with the error, instead of doing it right away. With TypeScript having such a strong type system, I'm surprised that it doesn't have anything like this for error handling
@ChrisgammaDE3 ай бұрын
Respect for beeing honest about realizing why it is this way. Also very important as a dev to be honest about beeing wrong
@StereoBucket4 ай бұрын
I've read about hoisting and understood it and then almost completely forgot about it for so many times in the past 8 years that I can't even keep count anymore.
@KAZVorpal4 ай бұрын
That hair and mustache look like something Weird Al would do as a parody of a failed 70s porn actor.
@HoussamElbadissi4 ай бұрын
I think try scoping makes total sense. The actual solution to the ergonomic issues created by that aren't solved by "removing" the scoping, but one option would be to make "try/catch" an expression (i.e. it returns a value, can be assigned to a variable). Something like this: const num = try { getNumber() } catch (e) { console.log(e); -1 } This is the way Kotlin works, and makes code very nice and readable. However, I'm not sure if this syntax is really fit for JS to begin with (the implicit return), so there's that. Bonus: it's also very TypeScript friendly, as it can automatically make a union type out of the return values for each of try and catch blocks (or `T | undefined` if one of them doesn't return).
@tekbal4 ай бұрын
Not a dumb thing. The fact that you do not realize that this behavior is by design is what truly sucks.
@MrManafon4 ай бұрын
Every day, I miss Elixir where a try statement may have a return value. Solves the issue of graceful fallback
@chadjaax4 ай бұрын
JS does that because it create a new scope whenever it encounter { }, you can even do something like main:{ // something somethig } and it will be an entire new space there. of course the facking var will escape it.
@einargs4 ай бұрын
Explicitly defining the variable outside makes it obvious that it needs to be handled to people coming in later. The better answer is the ability to use try as an expression. Theoretically no scope could make sense in a different operator where catch guarantees you leave the function.
@MaxPicAxe4 ай бұрын
What we need is post-initializable constants in JS and TypeScript should be able to handle this in the type system to ensure no funky behaviour.
@londek4 ай бұрын
I thought it's default mindset that anything in any { } should create a scope
@byebaum4 ай бұрын
Applying normal scoping to a try block encourages you to put code in the try that doesn't relate to the error condition that the catch is intended to deal with, but which needs to access values computed in the try. I prefer for try blocks to be as small as possible, and only cover the code which the catch deals with, but that then complicates getting data out of the try block because of the scoping rules.
@ProfessorTimbo4 ай бұрын
Your 2-space-width tabs make you my sworn enemy.
@ocks_dev_vlogs4 ай бұрын
Having the try/catch statements both be something that is considered their own code blocks is honestly something I agree with. Try only runs code up until an error is thrown, in which case it then runs the catch code, so if you define a variable within a try block but it crashes before it reaches this definition, then using this undefined variable outside the try statement wouldn't be possible. I also like the brackets counting for consistency, where every block created with {} is its own block. It would be really strange if try was the only block with brackets that didnt create a scope, and since it creates scope it prevents the accessing of a variable that doesn't exist.
@CottidaeSEA4 ай бұрын
One of the things I have a problem with when it comes to JavaScript is that you often have absolutely no clue which functions may throw. It's crazy.
@marloelefant75003 ай бұрын
5:30 is not quite right. If get number() throws an error, num won't exist as well. An exception stops the execution of the try block and jumps straight to the next catch block. Consequently, the assignment to num never happens.
@minikame22724 ай бұрын
I started pulling my hair out thirty seconds into this video but by the end was so fucking happy. Not just that you flipped sides (although as you say the DX does suck) but because it takes a distinct absence of ego to call BS on your own position after spending nearly ten minutes explaining it in public. If only more debates and conversations were had in good faith like this. Alright I've been watching you for like two years ago but because of this particular one, yeah okay, I'm finally clicking the fucking sub button.
@maciejcisowski70154 ай бұрын
This should not take a toy example and this amount of soul-searching from any dev with a modicum of experience.
@unalive_me4 ай бұрын
It should be changed to use the try statement where an error will occur, and have a catch block, that must return, follow that try statement. That way you're forced to handle the error if you need a value set: try logSomething(); try const num = mightError(); catch (e) { console.err(e); return -1; } const double = num * 2; return double;
@lackofsubtlety66884 ай бұрын
I am glad you changed your mind, because it makes 100 percent sense.
@AndreGreeff4 ай бұрын
just for the sake of clarity around the 3:00 mark: obviously, moving `const num` into the `try {}` block while leaving `const double` outside of said block will break, because that's how ES6 and the "block-scoped" `let` and `const` were designed to work. these did not replace `var` as everybody loves to tout. `try`/`catch` was around long before ES6, if you use `var num` (which is not block-scoped) inside the `try { }` block, then `num` is accessible outside of the `try`/`catch`, and you won't get a "ReferenceError: num is not defined"... this is only an issue today because we have different "building blocks" to play with, blocks that the designers of the other, older "blocks" did not foresee. my point is: when you use `var`, then `try`/`catch` makes sense. if you use `let` and/or `const`, then it does not.
@AndreGreeff4 ай бұрын
but wait... it doesn't "hoist `var` out" of the block, `var` is simply function-scoped, not block-scoped.
@artman12124 ай бұрын
The problem is not with the try/catch but with the entire concept of throwing errors.
@AndrewSmithDev4 ай бұрын
Result types are just much better. In JS you can just ignore an error and crash your entire program. You may not ignore it. You may just be oblivious about it but the result is the same. Your program crashes. With a result type, you're forced to handle the error. There's also no type for the caught error because you can throw anything as a JS error. If there was a result type then we could type the error.
@ritsu1334 ай бұрын
custom classes that extend Error /w instanceof checks I mean you can write `throw 1` but I feel like it's a developer's problem if they do that. result types are verbose BTW and you don't need to always handle the error really, although I kinda agree
@travistarp74664 ай бұрын
@@ritsu133 How do you know the possible errors a block can throw.
@theairaccumulator71444 ай бұрын
Nah they're really much worse. You don't care about 99% of errors that are thrown and just want them to bubble up to a request/global handler so they can be logged and an error response can be sent. Having thousands of if err != nil return nil, err just makes the code less readable and prone to typos and it doesn't solve the problem of having different types being thrown. Typescript at least lets you specify the error you care about in the catch block but in JS you have to do an instanceof check and then throw the ones you don't care about back which is the same thing under the hood just a little more terse. But it's still better than 2 if checks for err != nil and err being the one you care about.
@JackBond12344 ай бұрын
On the other hand, "every code block is a contained scope" is good in its simplicity. It'd be nice if there were some alternative that doesn't require a closed block though
@byebaum4 ай бұрын
I think the better solution to using an uninitialized variable is for it to be an error to use a variable whose var statement hasn't been reached, just like it is for a let or const.
@beorntwit7114 ай бұрын
This is an argument i heard a while back, where the guy was pushing 'await' with .catch notation.
@asailijhijr3 ай бұрын
Does the blocking behaviour of the try block also happen in a do{}while()?
@asailijhijr3 ай бұрын
I commented too early. Just like the video says, yes, it does.
@KaiHenningsenАй бұрын
There's a reason try{} is a scope for declarations in pretty much every language that has try{}.
@blacky78012 ай бұрын
This is the exact reason why they made var hoist variables. Beginners declaring variables within if blocks and wondering why they cannot access the variable outside the if block.
@Thomasvanlankveld4 ай бұрын
My workaround is wrapping the try/catch in an iife, so that I can just put the return value of the fallible call in a const and get on with my life
@gabrielcastilho41684 ай бұрын
You can fix that by allowing to return from try into a variable. Rust's do that, you can assign any block to a variable
@Bu7MaiD0754 ай бұрын
Imagine having all of your variables not being destroyed after the try block.
@RoyRope4 ай бұрын
Hmm for me it actually makes perfect sense, it is expected part of the try clausule is not executed(completely) so variable declarations can not be assumed after the try clausule.
@funkijote4 ай бұрын
Why not use a Go-style error handling pattern in JS as implemented in the 'attempt-promise' JS package? That is, wrap a function in a sort of try/catch utility function that returns a 2-item array containing either an error or the desired result in positions 0 and 1 respectively, and then use regular if statements to do one thing if err, and another if not err. So, for example, inside of some function we use a wrapper called 'attempt': const [err1, user] = await attempt(User.find(id)); if(err1) { /* ...handle error and return */ } /* ...do some stuff with user */ const [err2, product] = await attempt(Product.find(id)); if(err2) { /* ...handle error and return */ } /* ...do some stuff with user + product */ return { user, product };
@theairaccumulator71444 ай бұрын
That reduces your performance by a lot because you're spamming heap allocations and GCs just to return a value from a function.
@funkijote4 ай бұрын
@@theairaccumulator7144 True probably, will devise a test this weekend of if/the scale at which it matters.
@funkijote4 ай бұрын
@@theairaccumulator7144 So, haven't tested scaled performance of my hackey solution yet, but it turns out the pattern I described (with a better underlying implementation obviously) has formally been proposed as a JS standard kzbin.info/www/bejne/m5mYg4ybpdeLers
@pooyaestakhry4 ай бұрын
Yes, but no. while try scoping can be painful to use in some or many cases, it would be more confusing to have blocks (code between curly braces) act differently when it's try and normally everywhere else
@Bittboy4 ай бұрын
Honestly, I believe the way it's setup is correct (given how JS works). If a for-loop has the same behaviors, why should try-catch be an exception? If the first baseman catches a ball thrown by the pitcher before you get there, you're OUT. You don't get to keep going all the way to the umpire/home plate - you're no longer active or assigned any role or position until you get called to bat again. If you're trying to define variables inside a try-catch but use them outside of it, then at least figure out why you're doing that instead of arguing about the implementation no different from other scopes. Obviously what I've said does not apply to other languages that were designed with these things in mind, but since JS already has a definition of a "scope" then it only makes sense to follow the standard, not create an entirely new one to please a few people.
@davorinrusevljan64404 ай бұрын
The "only" reason is exactly why it should be the way it is.
@harryhack914 ай бұрын
2:55 Me, as a Java developer: "I don't see the issue here"
@steadexe4 ай бұрын
I think the only thing we can all agree, is that error handling in javascript (try/catch) is ugly. If only there was a middleground between callback (returning error and data) and promise. Like a sugar to return an array of error and data like golang but for promises.
@DanKaschel4 ай бұрын
Rust handles this so nicely. But in js i would love a try catch that looked like a ternary where the second value is a function with an exception argument. If the first returns a value, the second must also. That way variables still end up getting set.
@simonhartley91584 ай бұрын
If there was some keyword other than var for this, once people were familiar, I'm sure it would be fine. For compiled languages, I've also seen the concept of flow scope, where the variable can still be accessed if nothing can potentially cause it to be uninitialized.
@InfinityN4 ай бұрын
This video is a whole ass skill issue.
@Blackilykat4 ай бұрын
6:30 "that's the only reason that try/catch could ever reasonably be thought to keep things in its own block" I rarely write js but almost every time I've handled errors I didn't immediately return. IMO try/catch is definitely control flow as it expects part of the code to not be executed some of the time, and when that happens it executes another block.
@Blackilykat4 ай бұрын
i did not finish the video before commenting
@bigmontz4 ай бұрын
If you open a random scope like { } without any kind if, else, try, whatever, the const and variables define there can be only be used there. This also happens in C/C++ and other languages. This comes back to memory and other resources allocation. So you are able to free those resources wherever it is not useful. Apart of this, it makes the code more consistent. Why should try block have a different behavior of any other block construction in the language? A block is a block and it should behave the same as it does.
@MrMudbill4 ай бұрын
Try-catch in JS is a mess for many reasons. My favorite pain points are scoping, as mentioned, as well as having to do if instanceof checks for different types of errors, and possibly worst of all not knowing which errors are potentially thrown at any given time. Considering you can do like in C++ and throw literally any value, it makes it very difficult to catch the errors gracefully because `error` will always be `any` type. It's just a lot of boilerplate to deal with.
@NateVolker4 ай бұрын
I use this HOC a lot because of how ugly try/catch is: const safe = (fn, valueOnError) => (…args) => { try { return [fn(…args), null]; } catch(e) { return [valueOnError, e]; } }
@anon_y_mousse4 ай бұрын
It's good that you came around to the correct way of thinking that this way of it working makes sense *and* that JavaScript sucks. Another example for you, consider a normal block in a language like C: int main ( void ) { int a = foo(); int b = bar(); { int c = a + b; } printf( "%d ", c ); return 0; } // will not work because `c` had scope only within that inner block, but would if the printf() call was made inside that inner block. This is an overly simplistic example, but it demonstrates the point all the same that a variable can leave an inner scope.
@lautarodapin4 ай бұрын
funny thing about python, now with the type hinting you can build an error handling system like rust or go OkType = TypeVar("OkType") ErrType = TypeVar("ErrType", bound=Exception) Result = Ok[OkType] | Err[ErrType] def check_field_exists(something): if something.does_not_exist: return Err(ValueError(f"Field does not exist")) return Ok(something)
@marloelefant75003 ай бұрын
According to "Clean Code" by Robert C. Martin, when this poses a problem to you, you are probably doing too much in a single function. They argue that error handling and nothing else should be done in its own function.
@imadetheuniverse4fun3 ай бұрын
the thing i don't get is how someone gets annoyed at try scope in the first place? like what are they trying to do that's so insane that a literal inner scope messed them up? every single piece of code you write is scopes upon scopes upon scopes. people don't get how to logic data in and out of scopes or what?
@abraham_o4 ай бұрын
Honestly I see no issues here, thank God theo changed his mind. I spend most of my time coding with Python where you'd catch a named exception depending of the event that caused the exception, the only reason why I see this as an issue is where you have deeply nested throw statements when your function is calling another function that calls a function that throws an exception. Thinking about it makes me nervous. 😂