W3C home > Mailing lists > Public > public-media-capture@w3.org > October 2014

Re: Our error-handling is broken (Re: Promise slides)

From: Silvia Pfeiffer <silviapfeiffer1@gmail.com>
Date: Thu, 2 Oct 2014 16:47:39 +1000
Message-ID: <CAHp8n2=P=wJ-yikHbooHSbEacn9PRbPPuBZTq0KVwN8EAYnYCQ@mail.gmail.com>
To: Jan-Ivar Bruaroey <jib@mozilla.com>
Cc: public-media-capture@w3.org
Jan-Ivar,

Are you saying that Mozilla is blocked on moving forward with a more
complete implementation of the webrtc spec because the spec is not using
promises?

I'm keen for Mozilla to be completely feature compatible with chrome and
would prefer Mozilla devs spent time doing that over worrying about
promises. But if it's promises that's holding you up, I'm more inclined to
accept them.

Best Regards,
Silvia.
On 2 Oct 2014 06:06, "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.
>
>
> 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:48:06 UTC

This archive was generated by hypermail 2.3.1 : Tuesday, 6 January 2015 21:24:50 UTC