In JS functions, the ‘last’ return wins
jakearchibald.com
jakearchibald.com
- 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...`return` and `throw` set the function's result. `catch` automatically unsets the function's result. How `finally` works can be explained from that.
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.
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.
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.
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...
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.
var let = []
let[0] = 1 // Syntax error because let [ conflicts with destructuring lexical declaration.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."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.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
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.
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.
I now want languages who's spec allows this insanity to have a sanitizer like C#'s to prevent more footguns.
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;
}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`.
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.
The two most common use cases I’ve seen so far:
1. Same as the article example. UI action when AJAX request completes
2. In NodeJS disconnecting from resources (db, redis) after you are done. Otherwise the script will hang and never exit.
Like closing/releasing a database connection.
while (true) {
try {
return;
} finally {
continue;
}
}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.
I try returning but finally it continues the loop.
Literally reads as written?
And yet, I expect this will soon appear in those "JS is weird, wtf" listicles.
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.
And yet, you know someone will and they'll feel very clever for it.
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.
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.
After reading this article, I feel slight annoyance. But maybe that is just me.
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.
By knowing all the bad parts, you’ll be able to write much better code, because you know what to look out for.
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
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.)
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.
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?
try {
return console.log('one');
} catch(err) {
return console.log('two');
} finally {
return console.log('three');
}
Tip: The results are "odd".Right! Those are the "odd" numbers! ;)
You're trying to return the result of console.log, which is undefined, which you cannot return explicitly
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 :)