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

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

From: Jan-Ivar Bruaroey <jib@mozilla.com>
Date: Thu, 02 Oct 2014 10:09:51 -0400
Message-ID: <542D5CAF.4030402@mozilla.com>
To: Harald Alvestrand <harald@alvestrand.no>, public-media-capture@w3.org
On 10/2/14, 5:00 AM, Harald Alvestrand wrote:
> On 10/01/2014 10:05 PM, Jan-Ivar Bruaroey wrote:
>> 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"));
>>
>
> Now this is interesting, not because of its relationship to 
> getUserMedia, but because of what it says about the additional 
> functionality of promises....
>
> In the callback world, we insist that gUM calls *one* function, *once*.
> In the promises world, we say that a promise can call *any number* of 
> functions - and which ones get called is not going to be obvious.

That's a mischaracterization, so I fear my example is confusing. Whether 
you use promises or callbacks, gUM only calls *one* success function, 
but that consumer function typically launches everything that follows, 
including potentially more asynchronous steps like 
peerConnection.createOffer and so on. That's a chain whether you use 
promises or callbacks, no difference.

What's different is that with the callback pattern, it's near impossible 
to NOT react with anything but total failure of all subsequent steps (I 
think we presume this to be OK in gUM/WebRTC as it tends to be OK in 
simple demos). In other words, there's no room for handling errors along 
the way like you can in synchronous code using try/catch.

Maybe this helps:

My example is uncommon, because people typically DON'T put in rejection 
handlers for every promise step, because that's analogous to using 
try/catch on EVERY statement (promise.catch(f) is after all an alias for 
promise.then(null, f):

    // Synchronous code analogy to my very non-gUM example above

    try { step1(); } catch (e) { log ("fail1"); }
    try { log("success1"); step2(); } catch (e) { log ("fail2"); }
    try {
       log("success2a");
       barf;
       log("success2b");
    } catch (e) { log("fail3"); }
    try { log("success3"); step4(); } catch (e) { log ("fail4"); }
    try { log("success4"); step5(); } catch (e) { log ("fail4"); }
    try { log("success5"); } catch (e) { log ("failure"); }

Which you would never do in most gUM/WebRTC demos! A more common thing 
is to omit rejection-handlers, letting errors "bubble up", just like 
people don't normally try/catch every statement). Here's that fiddle [1]:

    <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"))
    .then(() => {
       log("success2a");
       barf;
       log("success2b");
    })
    .then(() => log("success3"))
    .then(() => log("success4"))
    .then(() => log("success5"))
    .catch(() => log("failure"));

This produces what you probably expected:

    success1
    success2a
    failure


And matches the pattern useful for simple gUM/webRTC demos.

> On the other hand, success4 got called exactly as expected, despite 
> the fact that something went boom somewhere in the processing. So if 
> you want to be sure 2 success handlers get called on success, you have 
> to do:
>
>    <promise-generator>
>               .then(success1, failure1)
>               .then(null, failure-in-handler-set-1)
>               .then(success2, failure2)
>               .catch(failure-in-handler-set-2)
>
> That's not necessarily a bad thing. But it's not unsubtle either.

Again I think you're taking the wrong lesson from my perhaps overly 
complicated example. In practice I think you'll see little reason to 
distinguish between the synchronous setup of an async function - like 
say peerConnection.createOffer - and the asynchronous part that follows, 
because if the setup fails then likely the async call wont go well 
either, but you're welcome to split things into as many parts as you 
wish. Again, for gUM and WebRTC, where each step tends to rely on the 
success of the previous step, the fiddle below is more appropriate. It 
just doesn't highlight all possibilities with error handling.

.: Jan-Ivar :.

[1] Firefox: http://jsfiddle.net/jib1/0h2jon2L - Others: 
http://jsfiddle.net/jib1/9dvbxmoh
Received on Thursday, 2 October 2014 14:10:24 UTC

This archive was generated by hypermail 2.4.0 : Friday, 17 January 2020 16:26:30 UTC