Adam Crabtree (2013-11-20T17:30:55.000Z)
domenic at domenicdenicola.com (2013-11-29T17:32:31.888Z)
Better error handling in JavaScript is incredibly important. As I see it, there are 3 fundamental roadblock or obstacles that undermine the utility of JavaScript's error handling: 1. Lack of typed catch 2. Lack of Error.create (solved from what I understand to some degree by Object.setPrototypeOf) 3. Lack of optimization of try/catch code (in V8)[1] I'll only talk about #1, but, briefly, #3 is additionally important because of its real world ramifications (e.g., node.js' explicit non-use of try/catch to avoid it[2]). I conjecture fixing #3 has not been made a higher priority because of the weakness of JavaScript error handling due to .#1 and #2. Regarding typed catch, typed catch is not just sugar. The above instanceof check works, but rethrowing obfuscates the reported throw line. node.js again is an example of a codebase that adapts to this by never attempting a typed catch to avoid rethrowing. Async code, control-flow, and async generators all largely owe their origins in JavaScript to try/catch. As an aside, node.js error handling is fundamentally broken and inconsistent and promises violate assumptions by catching errors originating in core.[3] Thunks (callback(err, result)) and consequently promises arose out of async programming in JavaScript and the inability to catch errors from a different event loop tick. Because try/catch could not catch errors in another tick, errors could not be caught and thus should not be thrown, and thus the thunk was born. Promises are a monadic alternative to callbacks, but not to try/catch unless you wrap all function calls (even synchronous non-IO) in a promise, which will slow your execution by separating synchronous execution across event loop ticks per the spec, effectively a non-starter. CPS is a true alternative to try/catch/throw, except for the lack of proper tail calls. So, we have 3 options: 1. Monadic approach (e.g., Maybe) 2. CPS (requiring a trampoline in lieu of PTC) 3. try/catch/throw (requiring workarounds to avoid DEOPT) There are tradeoffs to each approach. #3 is the language default since the first two have significant architectural implications, or at least require opt-in. There is technically a 4th common alternative of never catching. In node though, this leads to requiring a process restart and DoS liability since core shares the same stack as userland (application) code and throwing places it into an undefined state by not allowing the stack to unwind (they should be catching). I wrote the async trycatch module[4] to solve this. *Summary:* With async generators and the ability to use the default language error handling construct (try/catch) across async IO, try/catch is the best error handling mechanism in JavaScript, no longer requiring ugly hacks like my trycatch module. However, without typed catch, error handling is ugly and inconvenient, resulting in anti-patterns like the following ```js try{ // some code JSON.parse(foo) // some code } catch(e) { /* not always what you think it is */ } ``` that typed catch would easily solve. [1]: https://code.google.com/p/v8/issues/detail?id=1065 [2]: https://github.com/joyent/node/issues/5149#issuecomment-15761301 [3]: http://nodejs.org/docs/latest/api/domain.html#domain_warning_don_t_ignore_errors [4]: https://github.com/CrabDude/trycatch