[HN Gopher] In JS functions, the 'last' return wins
___________________________________________________________________
In JS functions, the 'last' return wins
Author : jaffathecake
Score : 89 points
Date : 2021-07-03 13:17 UTC (9 hours ago)
(HTM) web link (jakearchibald.com)
(TXT) w3m dump (jakearchibald.com)
| jonny_eh wrote:
| ok, so what does this print? try {
| return console.log('one'); } catch(err) {
| return console.log('two'); } finally { return
| console.log('three'); }
|
| Tip: The results are "odd".
| edflsafoiewq wrote:
| What's "odd" about it? It does exactly what I'd expect.
| re wrote:
| Not sure I'm call them odd -- it prints the same thing that
| would be printed without the "returns" (i.e., if the blocks
| contained only console.log calls), which is what I'd expect it
| to print: 'one' followed by 'three'. You're maybe thrown off
| because, while only one return statement can successfully
| return a value from the function, more than one can be
| executed, with their values evaluated.
| valbaca wrote:
| Uncaught SyntaxError: Illegal return statement
|
| You're trying to return the result of console.log, which is
| undefined, which you cannot return explicitly
| [deleted]
| cousin_it wrote:
| Reminds me of the unescapable loop: while
| (true) { try { return; } finally
| { continue; } }
| moritzwarhier wrote:
| Nice. :)
|
| Looks like a good minimum example to show that the intuitive
| natural language semantics of try and finally don't work with
| statements like continue that change control flow.
| bobbylarrybobby wrote:
| Reminds me of VBA, where the way to "return" a value in a
| function is to assign it to the function's name
|
| Sub f() 'returns 1
|
| f = 1
|
| End
|
| I always thought this was insane, but it does make the semantics
| clear. Maybe not so insane after all
| masswerk wrote:
| This makes a point for separating the semantics of (a) control
| flow (a premature exit, similar to break in a loop) and (b)
| assigning return values.
|
| However, JavaScript actually started with rather quirky
| semantics for return: if any exit returned a value, all exits
| had to return a value, otherwise an error was thrown. Yet
| another effect of the nexus of control flow and assignment of a
| return value. (This got fixed with the introduction of the
| return object as workaround, which was, I think, in ECMA-Script
| 3.)
| TheSoftwareGuy wrote:
| FORTRAN does the same actually
| masswerk wrote:
| As did Algol60.
| benibela wrote:
| And Pascal
| the_gipsy wrote:
| Why can't they get straight to the point?
| jaffathecake wrote:
| You'd be surprised how many people don't know about 'finally'
| at all. Given how many of JS APIs are async, there hasn't been
| much call for 'finally', so folks haven't encountered it. I
| think they're useful again with async functions, which is why I
| documented that. You might be _very smart_ and know that stuff
| already, but the article wasn't written specifically for you.
|
| Having worked at the BBC, I'm a fan of the Reithian principles;
| Educate, entertain, and inform. Yes, there's a central point to
| the article, but I like using that as a starting point to look
| at related things, like async functions, and the promise stuff.
|
| TL;DR: It's an article, not a tweet :)
| ludamad wrote:
| Just as another assurance, the article was not hard to skim
| for the main point
| cmrdporcupine wrote:
| > Please never quiz folks about this in a job interview.
|
| And yet, you know someone will and they'll feel very clever for
| it.
| swagasaurus-rex wrote:
| On a whiteboard
| ludamad wrote:
| "Please never quiz folks about this in a job interview." I have
| seen these types of 'well if they get this it's a good sign' type
| of questions. I guess if you really want that you could ask "What
| is a little known feature in a language you know well?"
| jaffathecake wrote:
| "Tell me about the weirdest bug you had to deal with" is a good
| way to cover this, and more. But really, you find out more
| about communication skills with this question than anything
| else.
| woutr_be wrote:
| A better question would be, especially for JavaScript; "what
| are the bad parts about JavaScript?". A person with actual real
| life experience will at least be able to list a couple.
| wizzwizz4 wrote:
| I think a better question would be "what are the good parts
| about JavaScript". _Everyone_ knows the bad parts (right?).
| woutr_be wrote:
| People know what they like about it (the good parts), it
| takes a more experienced person to be able to explain what
| they dont like.
| wizzwizz4 wrote:
| For most things, yes. But this is JavaScript, the
| language where intransitive equality, implicit object-to-
| primitive casting and mutable global objects _must_ be
| used in all slight-above-trivial code. Where `typeof` is
| useless and wrong, but nonetheless necessary; where there
| are seven different ways to parse a number, with six
| different semantics... there 's a reason that
| "JavaScript: The Good Parts" is such a short book.
| https://blog.klipse.tech/assets/js_good_parts.jpg
|
| Somebody who knows the good parts of JavaScript is a
| JavaScript expert. You want somebody who can write good
| JavaScript, and using the bad parts well has _nothing_ on
| using the good parts adequately.
| rgoulter wrote:
| As always, I think discussion really helps back it up.
| But I'd expect someone who has had experience with some
| language/library/framework will have run into problems
| with it, and so will be able to discuss some problem
| which hints at that experience.
| edflsafoiewq wrote:
| return x can be understood as ret = x; //
| assign to the implicit return variable return; // return
| ret to the caller
|
| If the return is cancelled by a finally, its basically just the
| case of assigning to the ret variable multiple times; the last
| assignment wins.
|
| There are a few languages where functions have an actual ret
| variable you can explicitly use.
| jaffathecake wrote:
| It's more of a 'result' variable, because the result can be a
| return or a throw. If you throw, it clears the return, and vice
| versa.
|
| I had something like this in the article originally, but then
| you also need to say that `catch` also does `_result =
| undefined`, and it felt messy.
| voidnullnil wrote:
| Blogs about "return" inside "finally" in Java and the likes were
| common in the 2000s. The fact that languages have so many simple
| things like this that surprise people indicates that there is a
| problem somewhere. I'm sure some of the older school languages
| (like VB maybe) had some insane semantics like an interrupted
| return will return null, I can't remember them in that much
| detail.
| valbaca wrote:
| low effort article explaining precisely what `finally` does in JS
| that is in no way surprising
| byteface wrote:
| I found finally is useful for kicking off a job after returning a
| response within the same func without having to spawn another
| process or block. i.e. return a 200 response immediately in the
| catch then kick your slow running job off in the finally. They
| can poll back for results when baked.
| dvt wrote:
| This article omits getting into try-catch-finally semantics, and
| it's not really that mysterious when you think about it
| (although, yes, please don't ask people about this in an
| interview):
|
| - A catch block is only executed if an exception is thrown in the
| try block.
|
| - A finally block is executed always after a try(-catch) block,
| if an exception is thrown or not.
|
| > As a side-effect, returning from finally clears a thrown error:
|
| Exceptions aren't "cleared," they are "finally-d." Because that's
| how finally _works_ [2]. The final block, if evaluated, always
| overrides the result of the previous blocks. A slightly more
| interesting example of try-catch weirdness is how catch blocks
| are one of the few constructs that create new scope (technically
| _augment_ scope): function F() {
| try { throw "error"; } catch (err) {
| err = { "hello": "world" }
| var hoisted = "foo" console.log(err)
| } finally { return hoisted; // works, since this
| gets hoisted to the top of F return err; //
| breaks, since err is spooky and only "scoped" in the catch block
| } }
|
| [2] https://tc39.es/ecma262/multipage/ecmascript-language-
| statem...
| dahart wrote:
| > Exceptions aren't "cleared," they are "finally-d." Because
| that's how finally works
|
| This explains exceptions thrown in the try block, but what
| about exceptions thrown in the catch or finally blocks?
|
| What is the reason for allowing returns in try-catch-finally
| blocks? Is there a good example of something that would be hard
| to handle otherwise? I feel like the article and examples here
| are all good demonstrations of why return statements should be
| avoided in try-catch-finally blocks.
| masklinn wrote:
| > What is the reason for allowing returns in try-catch-
| finally blocks?
|
| Disallowing them is more work.
|
| I don't dislike C# forbidding flow control in finally blocks,
| but I can well understand language designers not caring.
| mjevans wrote:
| From an assembly focused point of view, multiple "return"
| functions executing is madness. Logically it makes far more
| sense for the flow of execution to be fully controlled by
| the return and absolutely depart the function.
|
| I now want languages who's spec allows this insanity to
| have a sanitizer like C#'s to prevent more footguns.
| jwalton wrote:
| > why return statements should be avoided in try-catch-
| finally blocks
|
| When I was a wee lad, learning basic and C, my father tried
| to impress upon me that a function should never have more
| than one `return` statement.
|
| Back in those days, all these languages had `goto`
| statements, and we were told never to use them, because they
| were bad. But goto isn't bad because "goto" is a bad word -
| it makes your life unnecessarily complicated. A goto makes it
| very hard to reason about code, because you never know how
| you got to a certain line of code. Line 21 comes after line
| 20, but it might also come after line 30 if line 30 has a
| `goto 21`.
|
| But, semantically, there's not a lot of difference between:
| int myfunc(int *ptr) { if (ptr == NULL) {
| return 1; } return 0; }
|
| and: int myfunc(int *ptr) { int
| result; if (ptr == NULL) { result =
| 1; goto END; }
| result = 0; END: return result; }
|
| When someone calls `myfunc`, and it returns, you don't know
| if it returned from the end of the function, or from the
| middle of the function. A function with multiple returns is a
| function full of gotos; or as my dad used to say: "Functions
| always have a single entry point; they should have a single
| exit point."
| aardvark179 wrote:
| I think it's important to reflect on old practices and
| consider why they were important, and whether they still
| are.
|
| Many of the reasons for only having a single return were
| that it made it much easier to reliably clean up resources
| allocated by the function. Given a language with garbage
| collection, or even simply try, catch, and finally, provide
| a better way to handle this. The mistake Javascropt makes
| is allowing a return in a finally block.
| RHSeeger wrote:
| I've never been a fan of the "only one return" view. I find
| it far easier to understand when the "preconditions" are
| checked at the beginning of the function and, if they fail,
| bail out early (guard clauses). Then there's less mental
| load in the rest of the function.
| jwalton wrote:
| I do this too. :P Especially in a language like Go or C
| where you don't have exceptions, I think the "fast fail"
| at the start of the function is a reasonable "exception
| to the rule". Otherwise you can quickly end up in a
| nested if statement hell.
|
| But back in the days when I was doing embedded work, I
| frequently came across problems where someone allocated
| some resource at the start of a function, freed the
| resource at the end of the function, but then either
| failed to realize there was a return in the middle of the
| function, or someone else came along later and added a
| return to the middle of the function; either way we ended
| up with a resource leak. I saw this pattern over and over
| again. "Fast fail" is reasonable design pattern, so long
| as it's at the top of the function and you're careful not
| to allocate things before you check for preconditions,
| but functions of the form: blah
| blah if blah blah
| return x else blah blah
| return y
|
| are (IMHO) an anti-pattern.
| Izkata wrote:
| Yeah, I'm pretty sure those leaks are the origin of it.
| At some point it became a "best practice" without
| understanding the reasons behind it, and people started
| applying it everywhere without any thought to "why".
| [deleted]
| twoodfin wrote:
| I typically take this a step further, returning from
| trivial or common "fast-path" cases up front and falling
| through to any complex logic.
| jonny_eh wrote:
| This has certainly become the the common best practice.
| I'm a fan too.
| kilburn wrote:
| > "Functions always have a single entry point; they should
| have a single exit point."
|
| Kind of moot as soon as you are using a language with
| exceptions, where by definition every function can have
| surprise exit points at every point where they call another
| function.
|
| Furthermore, the whole "goto is evil" ordeal has been
| debunked by the top professionals in our field [1] and
| discussed many times here on HN [2,3,4,5].
|
| [1] https://koblents.com/Ches/Links/Month-
| Mar-2013/20-Using-Goto...
|
| [2] https://news.ycombinator.com/item?id=18484221
|
| [3] https://news.ycombinator.com/item?id=19959592
|
| [4] https://news.ycombinator.com/item?id=14540088
|
| [5] https://news.ycombinator.com/item?id=8760518
| dvt wrote:
| > What is the reason for allowing returns in try-catch-
| finally blocks?
|
| I think it just makes for a simpler language spec, and in
| this case, return is treated like any other statement; then
| again, there's a note here specifically about try-catch-
| finally blocks[1] so maybe it _doesn 't_ make for a simpler
| language spec :)
|
| [1] https://tc39.es/ecma262/multipage/ecmascript-language-
| statem...
| jchw wrote:
| I actually don't think it's to make the language simpler,
| but most likely for compatibility. First, Statement/Block
| are parameterized with whether or not they can contain
| Return statements in the spec, so it wouldn't complicate
| the spec much. Second, it is exceedingly rare that JS
| compatibility breaks. The one recent break I am aware of
| is: var let = [] let[0] = 1 //
| Syntax error because let [ conflicts with destructuring
| lexical declaration.
| dahart wrote:
| Yeah, that's my thinking - it seems simple at first, but
| when you see that return and finally when used together are
| in conflict with their stated / expected behaviors and that
| the language specifies the tie breaker in a way that might
| be unexpected... wouldn't it potentially be simpler both in
| the spec and to a developer to make return in try-catch-
| finally blocks a syntax error?
|
| I'm assuming there's a reason to allow the return
| statements in try-catch-finally blocks, but I can't think
| of a use-case. The only use for a return inside a finally
| block would be to cancel a return in a try or catch block,
| right? In what cases is that actually desirable? I can see
| wanting to return from catch, because something bad
| happened. Overriding that return in finally doesn't make a
| lot of sense to me, but I'm certain the ES designers
| thought about it more than I did.
| jaffathecake wrote:
| The way I think about it:
|
| `return` and `throw` set the function's result. `catch`
| automatically unsets the function's result. How `finally` works
| can be explained from that.
| dnautics wrote:
| It's akin to how in pascal you set the return value by
| assigning the name of function. I think. It's been decades
| thijsvandien wrote:
| Or the variable `Result` (most common nowadays), although
| you may also call `Exit()` with a parameter.
| jonny_eh wrote:
| > `catch` automatically unsets the function's result.
|
| Do you mean finally? Even so, it doesn't automatically unset
| the result, it's just _able_ to overwrite it. If a finally
| doesn 't execute a return or throw, the previous one is used.
| jaffathecake wrote:
| I don't mean finally, because it'd be wrong for the reasons
| you list.
|
| catch however always unsets the result. If you don't
| manually throw or return from a catch, then the result is
| an empty result (which is JavaScript is a return of
| undefined)
|
| finally doesn't do anything automatically, but a throw or a
| return changes the result.
| e12e wrote:
| > return err; // breaks, since err is spooky and only "scoped"
| in the catch block
|
| Both err's? (the argument and the one you define)? How about:
| try { throw "error"; } catch (err) {
| var e = err; // is this different from not shadowing err?
| console.log(e) } finally { return e;
| }
| sangeeth96 wrote:
| OP didn't redefine `err`, just assigned a new value to the
| `err` variable that was available as `catch`'s parameter.
| Only the catch's error variable is scoped to the catch block.
| Any variable that's defined inside `catch` block using `var`
| is hoisted in the scope of the nearest function.
|
| So, the `return e` in your example works since `e` was
| hoisted when you defined it using `var e = err`. If you tried
| `return err` instead, it'd result in a `ReferenceError`.
| e12e wrote:
| > OP didn't redefine `err`, just assigned a new value to
| the `err` variable that was available as `catch`'s
| parameter.
|
| How would you define "redefine" if this isn't an example of
| redefining?
|
| > the `return e` in your example works since `e` was
| hoisted when you defined it using `var e = err`. If you
| tried `return err` instead, it'd result in a
| `ReferenceError`.
|
| Ok, thank you.
| jchw wrote:
| nit: You never declare hoisted at all. Wouldn't that assignment
| cause globalThis.hoisted = "foo" ? I assume you meant to
| declare it somehow, but it will only work if you declare it
| with var, which isn't block-scoped to begin with.
| dvt wrote:
| Totally correct, fixed now! I omitted the var by design since
| I was just trying to elucidate the difference between hoisted
| and err (the var keyword breaks that flow a bit).
| Smaug123 wrote:
| F# lacks an imperative "return" at all - a function returns
| precisely the value of the expression that is its body - which
| makes it a lot easier to think about this kind of thing, at the
| cost of sometimes having to be explicit about assigning a value
| to the result of a `try/with`.
| TrowthePlow wrote:
| This seems much less about which return actually 'sends data
| back' (terminates function execution) and more about how
| try/finally works in general
| SavantIdiot wrote:
| > I hadn't used finally much in JavaScript until recently, where
| I find myself using it like this in async functions:
|
| We can tell by your notation. Mixing try blocks with async/await
| and promises is mixing patterns and obscuring what you are
| attempting to accomplish.
| jaffathecake wrote:
| Nope. It was designed precisely for patterns like this.
| SavantIdiot wrote:
| Really? I don't follow: why is try/catch/finally preferred
| over: async function someAsyncThing() {
| startSpinner(); await asyncWork() .catch(err
| => { if (err.name == 'AbortError') return;
| showErrorUI(); }) .finally(() => {
| stopSpinner(); }); }
|
| Should be identical, no?
|
| EDIT: Well, except that if there was more after asyncWork()
| the return in the try block would exit someAsyncThing,
| whereas the return in the catch block just returns from the
| catch. Is that his point?
| locallost wrote:
| I doubt I know a single person that knows this (me included,
| until now at least). Fortunately I've never seen this actually
| being used. It's a bit troubling that the promise version behaves
| differently because most people view try/catch to be generally
| equivalent to the code that uses promises. I'm making a note to
| link to this article the first time I see it in a PR :-).
| lucideer wrote:
| > _The same happens in Java and Python too._
|
| And yet, I expect this will soon appear in those "JS is weird,
| wtf" listicles.
| karldcampbell wrote:
| Doesn't javac issue a warning for this? I know most IDEs do.
| whoisthemachine wrote:
| C# does not allow returns from a `finally` block, which has
| annoyed me at times. However, seeing the consequences of
| allowing the behavior realized in this article, I am happy for
| the rule.
| masklinn wrote:
| > C# does not allow returns from a `finally` block
|
| I think C# does not allow any flow control in finally blocks,
| so no continue or break either (well for continue and break
| they're allowed if the statement they "refer to" is also in
| the finally block, but flow control can't go through or
| escape from the block).
|
| Essentially in C# a finally block can only be escaped from by
| falling off of the block's end.
| ajkdhcb2 wrote:
| I finished reading this and felt that it was annoying clickbait.
| The example it started with (that I saw in a preview before
| clicking) was just misleading
| natenthe wrote:
| This reminds me of something that would be included in a JS
| engineering interview. I find questions like this to be
| ridiculous. Knowing the answer to a question about this 'gotcha'
| would be a pretty terrible signal about future engineering
| performance. I'll focus on writing good software and solving
| complex problems, thanks.
|
| After reading this article, I feel slight annoyance. But maybe
| that is just me.
___________________________________________________________________
(page generated 2021-07-03 23:01 UTC)