Use ".then(null, Cu.reportError);" with Promise.jsm

Myk Melez myk at mozilla.org
Tue Aug 13 08:28:32 UTC 2013


On 2013-08-12 16:44, Mark Hammond wrote:
> But isn't it true that exceptions in any of those bodies would remain 
> unreported?  ie, if the inner was changed to:
>
>           step3.then(result => {
>             throw "oh no";
>           });
>
> wouldn't it need to be:
>
>           step3.then(result => {
>             throw "oh no";
>           }).then(null, Cu.reportError);
>
> to see that exception?  And thus each of the .then() calls above needs 
> the trailing .then()?
Yes, as written. But your code example is probably unnecessarily 
complex. And even if it isn't, simply returning the promises the then() 
calls produce will chain them and let you use a single "reject" handler 
to handle exceptions in them.

In your example, step1, step, and step3 are clearly promises (or 
function calls that return promises), and the arguments to step1.then() 
and step2.then() are anonymous functions that only call step2 and step3, 
respectively; which is the exact equivalent of passing step2 and step3 
directly to those functions.

So your "nested" example is functionally equivalent to the version you 
said the change "should look like," and throwing in the "innermost" 
promise (or, indeed, any other) propagates the "rejection" to the last 
"reject" handler in the chain:

   something.then(step1).
             then(step2).
             then(step3).
             then(result => {
               throw "oh no";
             }).
             then(null, Cu.reportError);

But what if you have to call intermediate anonymous functions, because 
they don't just call step2 and step3 but also do some work? Then you can 
still chain them sequentially:

   something.then(step1).
             then(result => {
               // do some work, then call step 2
             }).
             then(step2).
             then(result => {
               // do some work before calling step 3
             }).
             then(step3).
             then(result => {
               // do some work after calling step 3
             }).
             then(result => {
               throw "oh no";
             }).
             then(null, Cu.reportError);

Ah, but what if you need those anonymous functions to close over the 
scopes of step1 and step2, respectively? Then you must nest the 
promises, but you can still rely on a single error handler. Just return 
each promise returned by a then() call, and they'll get properly chained:

   something.then(result => {
     return step1.then(result => {
       return step2.then(result => {
         return step3.then(result => {
           throw "oh no";
         });
       });
     });
   }).then(null, Cu.reportError);

FWIW, I tested these examples in the Browser Console by prepending this 
code snippet to them:

   let { Promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
   let something = Promise.resolve("something");
   let step1 = Promise.resolve(1);
   let step2 = Promise.resolve(2);
   let step3 = Promise.resolve(3);

> [If nothing else though, this thread shows that it's extremely hard to 
> write code around this that is "obviously correct"]
Promises have a learning curve. I've been on the curve for several weeks 
now, and I'm still wrapping my head around them. But once you get the 
hang of them, and especially in combination with Task.jsm, they really 
simplify the readability of complex control flows, especially those 
mixing synchronous and asynchronous calls.

So they're worth continuing to grapple with!

-myk




More information about the firefox-dev mailing list