- From: Eric Rescorla <ekr@rtfm.com>
- Date: Wed, 1 Oct 2014 23:15:15 -0700
- To: Jan-Ivar Bruaroey <jib@mozilla.com>
- Cc: "public-media-capture@w3.org" <public-media-capture@w3.org>
- Message-ID: <CABcZeBORDdQQRoxj3xzVqSe6yvZ48QA4WZsEKqZn+aOVwbrZFw@mail.gmail.com>
On Wed, Oct 1, 2014 at 1:05 PM, Jan-Ivar Bruaroey <jib@mozilla.com> wrote: > In case it's not obvious from the slides, I'll be arguing tomorrow that > our error-handling is broken and should not be shipped. > > This excellent post on promises [1] says why better than I can: > > "There are two very important aspects of synchronous functions: > > They return values > They throw exceptions > > Both of these are essentially about composition. That is, you can feed > the return value of one function straight into another, and keep doing this > indefinitely. More importantly, if at any point that process fails, one > function in the composition chain can throw an exception, which then > bypasses all further compositional layers until it comes into the hands of > someone who can handle it with a catch." > > Now, in an asynchronous world, you can no longer return values: they > simply aren’t ready in time. Similarly, you can’t throw exceptions, because > nobody’s there to catch them. So we descend into the so-called “callback > hell,” where composition of return values involves nested callbacks, and > composition of errors involves passing them up the chain manually, and oh > by the way you’d better never throw an exception or else you’ll need to > introduce something crazy like domains. > > Conveniently, we already agreed months ago that in WebRTC and gUM, error callbacks are used for all runtime error reporting and exceptions are used *solely* for programming errors (i.e., the kind of thing that would be a good candidate for compile-time failures and/or asserts in other languages). Thus, this argument about exceptions is largely irrelevant, leaving us with an argument about the relative aesthetic merits of callback chains versus promises. -Ekr I will demonstrate. > > First, here's a fiddle [2] showing how promises handle errors correctly: > > > <div id="log"></div> > > var div = document.getElementById("log"); > var log = msg => (div.innerHTML = div.innerHTML + msg + "<br>"); > > new Promise(resolve => resolve()) > .then(() => log("success1"), () => log("fail1")) > .then(() => { > log("success2a"); > barf; > log("success2b"); > }, () => log("fail2")) > .then(() => log("success3"), () => log("fail3")) > .then(() => log("success4"), () => log("fail4")) > .then(() => log("success5"), () => log("fail5")) > .catch(() => log("failure")); > > This outputs: > > success1 > success2a > fail3 > success4 > success5 > > Importantly, fail3 - not fail2 - is called because success2 which barfed > is actually step3. > > Furthermore, success4 and success5 proceed because we "caught" the error > in fail3! (we'd need to re-throw the error just like we'd do in a try-catch > clause if we didn't want that). > > Now, here's the same test in a fiddle [3] showing how callbacks handle > errors poorly (spot the bug): > > > <div id="log"></div> > > var div = document.getElementById("log"); > var log = msg => (div.innerHTML = div.innerHTML + msg + "<br>"); > > try { > oldcall(() => { > log("success1"); > oldcall(() => { > log("success2a"); > barf; > log("success2b"); > oldcall(() => { > log("success3"); > oldcall(() => { > log("success4"); > oldcall(() => { > log("success5"); > }, () => log("fail5")); > }, () => log("fail4")); > }, () => log("fail3")); > }, () => log("fail2")); > }, () => log("fail1")); > } catch(e) { log("failure"); } > > function oldcall(success, failure) { > var succeed = true; > setTimeout(succeed? success : failure, 0); > } > > This outputs: > > success1 > success2a > > > and a "ReferenceError: barf is not defined" in web console. > > This is bad because it means the program can't handle the error. The bug? > try/catch is needed around success2 and - to be safe - around *every* > callback! This final fiddle [4] shows what's minimally needed to handle > errors safely in our API, and even this doesn't truly propagate errors, > e.g. we're just pushing handlers in, not allowing errors to rise up: > > > var div = document.getElementById("log"); > var log = msg => (div.innerHTML = div.innerHTML + msg + "<br>"); > > try { > oldcall(() => { try { > log("success1"); > oldcall(() => { try { > log("success2a"); > barf; > log("success2b"); > oldcall(() => { try { > log("success3"); > oldcall(() => { try { > log("success4"); > oldcall(() => { try { > log("success5"); > } catch(e) { log("failure"); } }, () => log("fail5")); > } catch(e) { log("fail5"); } }, () => log("fail4")); > } catch(e) { log("fail4"); } }, () => log("fail3")); > } catch(e) { log("fail3"); } }, () => log("fail2")); > } catch(e) { log("fail2"); } }, () => log("fail1")); > } catch(e) { log("failure"); } > > function oldcall(success, failure) { > var succeed = true; > setTimeout(succeed? success : failure, 0); > } > > This error-prone boilerplate is reminiscent of antique languages without > exception-handling, and has this output: > > success1 > success2a > fail3 > > This STILL doesn't have the same output as the first fiddle, and I gave > up since I don't think a version that does is feasible without promises. > > Could we at least hide the boilerplate inside oldcall? We could, but this > would require a third argument (recall that we want fail3 not fail2): > > > function oldcall(success, failure, followingFailure) { > try { > var succeed = true; > setTimeout(succeed? success : failure, 0); > } catch (e) { followingFailure(e); } > } > > The third argument would have to be optional to be backwards compatible, > so there would be no enforcement. Even then it would be complicated to use, > and still wouldn't solve propagation. > > The proper way to solve this is with promises. > > .: Jan-Ivar :. > > [1] > http://domenic.me/2012/10/14/youre-missing-the-point-of-promises/#what-is-the-point-of-promises > [2] Firefox: http://jsfiddle.net/jib1/eyq80vh6 - Others: > http://jsfiddle.net/jib1/68h59cbf > [3] Firefox: http://jsfiddle.net/jib1/79qLeb4g - Others: > http://jsfiddle.net/jib1/qyqypsyv > [4] Firefox: http://jsfiddle.net/jib1/cy4rvpLb - Others: > http://jsfiddle.net/jib1/xh5r933a > >
Received on Thursday, 2 October 2014 06:16:24 UTC