4 ms·
Exactly! When you count for nested try/catchs you see no significant improvement
by msoad 10y ago
Exactly! When you count for nested try/catchs you see no significant improvement
- WorldMaker 10y agoWhy would you nest try/catches? At worst you get try/catch parades: try { await thing1() } catch (e) { console.log(e) } try { await thing2() } catch (e) { console.log(e) } // ... and so forth ... That's still nothing like the pyramids you get in callback world.
- zzzcpan 10y ago"Pyramids" are not that useful for complex flows, just as synchronous-looking code, but for something as simple as your example they are equivalent in complexity, maybe even less complex, because they don't have additional implicit behavior introduced by async/await. Still, worlds better, than CSP with channels.
- WorldMaker 10y agoasync/await ends up being the less complex thing, my "simple" example is an extreme that sometimes but rarely happens. When was the last time you saw that in synchronous code? It happens, certainly but the synchronous default is to only handle what you can handle and then pass everything else back up the chain. The asynchronous default with async/await is the same, and you can rely on default error propagation in the natural default case: async function doTheThing() { await thing1() await thing2() } That just works, if there's an error in thing1(), doTheThing() stops at that line and returns the rejected promise to whatever called it. The callback world is clearly the more complex here, where all error handling has to be properly wired and there is no default handling. The node callback equivalent to the above is: function doTheThing(callback) { thing1(function (error, value) { if (error) callback(error, null) thing2(function (error2, value2) { if (error2) callback(error2, null) callback(null, true) }) }) } You can't forget either if (error) check or else errors will just silently be eaten/ignored. The "implicit behavior" introduced by async/await are essentially the same things you are used to in code without async/away, such as the way try { } catch { } and throw naturally work in synchronous. That alone should be considered a simplifying improvement.
- zzzcpan 10y agoFirst of all, exceptions are not a great way to handle errors, because they are implicit. There is no special keyword, like "await", to permit a function to throw exception, you have to guess an implicit behavior of each function. And you will have functions that never throw exceptions, functions that do, functions that lie about whether they do or do not. You will have to remember that at all times and be very careful to always use try/catch if unsure and so on. It's a big cognitive load to have. Most of the times the easiest and the only reasonable way to deal with errors is to be explicit about them. So, the equivalent to that code with callbacks is the one from your previous example and not the simplified one that doesn't handle errors. Not forgetting "if (error)" in this case will be exactly the same, as not forgetting "catch (error)" or "if (error)" if you handle errors properly in the first place. But these are just patterns and with enough consistency they don't introduce much cognitive load. What does is implicit behavior and you don't have one with callbacks in your example, but you do with await. It's not as bad, as with threads, but still a little bit worse, than with callbacks.
- WorldMaker 10y agoI'm sorry you don't like exceptions. It's a pattern that's pretty well understood at this point, has been in the JS language for quite some time now and other languages for even longer. «So, the equivalent to that code with callbacks is the one from your previous example and not the simplified one that doesn't handle errors.» No, it isn't. It really isn't. The callback version isn't doing any sort of handling of errors, it's just laddering them, passing them back up the chain. The async/await version is doing the same automatically, wiring together for you the error handling to pass errors back to calling functions. Even if you did want to handle every possible error/exception that could be thrown in doTheThing(), you don't really have to go all the way to the "parade" version, as only one try/catch should be sufficient in doTheThing(). The issue here is that where you see error handling as a cognitive load, the general best practices for error handling have mostly leaned to the goal that exceptions are for actual exceptional cases and catching exceptions should be left to cases where the user can actually fix the exceptional situation (rare) or logged somewhere appropriate in a global handler. If you are unsure if you should handle an exception, don't handle the exception. Let the calling function catch, or let it continue bubbling up to whatever global exception handlers you want to set, or even just leave them for the browser's Unhandled Exception Handler and Unhandled Rejected Promise Handler to spit out debug information in its dev tools console. Don't bother remembering anything, just let your tools do their job. The difference between catch (error) and if (error) in the callback example is that if you "forget" catch (error), it bubbles up to your browser's handlers, but if you forget if (error) it doesn't stop execution, it doesn't bubble up to your global toolkits and exception handlers and it doesn't bubble up into your dev tools debuggers. (One thing I realized in writing this: due to my rustiness in callback coding I forgot that they should be if (error) return callback(error, null) to properly stop execution, which you want in the default case of not handling the error but passing it back up the chain. Small mistake that could cause debugging headache and adds to my point that the little things that are easy to forget that you always must do in the callback world can have a major impact on debugging...)
- msoad 10y agoThat won't work because if thing1() throws you usually don't want to process to thing2 Take this for example: const response = await fetch('./api.json'); try { const json = await response.json(); console.log('YAY! JSON!', json); } catch (jsonParseError) { alert('damn it!'); } } catch(fetchError) { console.warn('this is not cool'); }
- WorldMaker 10y agoOkay, that's a fair counter-argument. My gut feeling is that's possibly one catch to many and as much as possible I'd want to try to merge the two catches/unifying the error response logic. Another idea is that you could reformat to a "parade" try/catch by adding returns to the catch: try { const response = await fetch('./api.json') } catch (fetchError) { console.warn('this is not cool') return // Exit } try { const json = await response.json(); console.log('YAY! JSON!', json); } catch (jsonParseError) { alert('damn it!'); } Obviously you'd need to move any code you'd want to run regardless of error up into the parent function or out into a wrapper function.