try / catch / finally
In short: try { risky } runs code that might throw. catch (e) { ... }
runs only if something inside try threw, and receives the error as e —
the program keeps running instead of crashing. finally { ... } always
runs afterwards, whether an error was thrown or not, whether it was caught
or not — it's for cleanup that must happen either way. One sharp edge: a
return inside finally overrides any pending return from try or
catch, so use finally for cleanup, not for return values.

Parsing a playlist file that might be broken
A real file can be valid or corrupted — try/catch handles both without
crashing:
The first call's JSON.parse succeeds, so catch never runs — but
finally still logs "done attempting to load". The second call's
JSON.parse throws a SyntaxError, catch handles it, the program
doesn't crash, and finally runs there too. Same cleanup message, every
time, regardless of what happened inside try.
finally runs even when try already has an answer
finally isn't optional cleanup that gets skipped once try has decided
to return — it always runs first:
try's return "from try" doesn't exit the function immediately — it
waits for finally to finish running its cleanup, and only then does the
function actually return. The order is: try decides to return, finally
runs regardless, then the function exits.
The trap: a return inside finally wins
If finally has its own return, it silently replaces whatever try or
catch was about to give back:
"from try" is discarded completely — finally's return "from finally"
wins, with no warning. This is almost never what you want, which is why
finally should hold cleanup (closing a connection, logging, resetting
state) and not its own return or throw.
Quick practice
When does the catch block run?
When does the finally block run?
What does function f() { try { return "A"; } finally { return "B"; } } return when called?
