Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Promises: is .done() executed always even if .catch() is?

My Promise issue

I am new to Promises and I've been reading the Q Documentation, where it says:

When you get to the end of a chain of promises, you should either return the last promise or end the chain.

I have defined a Promise in my code the Q.Promise way, with the following console.logs to log out an execution trace:

function foo(){
   return Q.Promise(function(resolve, reject) {

    doSomething()
    .then(function() {
      console.log('1');
      return doSomething1();
    })
    .then(function() {
      console.log('2');
      return doSomething2();
    })
    .then(function() {
      console.log('3');
      return doSomething3();
    })
    .catch(function(err) {
      console.log('catch!!');
      reject(err);
    })
    .done(function() {
      console.log('done!!');
      resolve();
    });

  });
}

In case every doSomethingN() executes correctly, everything works as intended and I get the expected trace:

1
2
3
done!!

But in case any of the doSomethingN() fails:

foo() works correctly, because the error function callback is the one that runs whenever a reject(err) occurs:

foo().then(function() { /* */ }, function(err) { /* this runs! */ });

And I get the following trace (ie. when doSomething1() fails):

1
catch!!
done!!

My question

What I thought at first was the following:

Okay, let's handle the chaining success and failure in both: .done() and .catch() methods. If everything goes well .done()'s callback will be executed and the promise will be resolved. In case there's an error at any point, .catch()'s callback will be executed and the promise will be rejected - and because of that, done() won't be executed.

I think I am missing something about how the .done() works... because by having a look at my logging trace, I realized that .done() seems to be executing always - whether there is an error and .catch() is executed or not - and that is what I wasn't expecting.

So, after that, I removed .done()'s callback and now foo():

  • works if there's an error during the chain execution
  • does not work if everything works correctly

What should I reconsider and how could/should I make it work?

like image 819
charliebrownie Avatar asked Nov 26 '15 21:11

charliebrownie


People also ask

Are promises executed immediately?

The Promise constructor takes a function (an executor) that will be executed immediately and passes in two functions: resolve , which must be called when the Promise is resolved (passing a result), and reject , when it is rejected (passing an error).

How does catch work in promises?

catch " around the executor automatically catches the error and turns it into rejected promise. This happens not only in the executor function, but in its handlers as well. If we throw inside a . then handler, that means a rejected promise, so the control jumps to the nearest error handler.

What does the finally () method on promise do?

The finally() method of a Promise schedules a function, the callback function, to be called when the promise is settled. Like then() and catch() , it immediately returns an equivalent Promise object, allowing you to chain calls to another promise method, an operation called composition.


2 Answers

catch(cb) is just an alias for then(null, cb), and you've actually fixed an error in catch, so flow naturally turned to success result in done.

If you want to just decorate the error in catch, you should rethrow the error afterwards, e.g. proper passthru may look as:

catch(function (err) {
   console.log(err);
   throw err;
});

Still your example doesn't make much sense. You should never use done, when you return a promise. If you want to resolve initialized promise with internally created chain of promises, you should just resolve it as:

resolve(doSomething()
  .then(function() {
    console.log('1');
    return doSomething1();
  })
  ....
  .then(function() {
    console.log('N');
    return doSomethingN();
  }));

There's no need for internal error handling, leave that to consumer of promise which you return.

And other point. If when creating new promise you know it will be resolved with other one, then there's no logical reason to create such promise, just reuse one you planned to resolve with. Such error was also coined as deferred anti-pattern

like image 141
Mariusz Nowak Avatar answered Oct 11 '22 06:10

Mariusz Nowak


You should consider doing this:

function foo() {
  // Calling .then() on a promise still return a promise.
  // You don't need Q.Promise here
  return doSomething()
    .then(function(doSomethingResult) {
      console.log('1');
      return doSomething1();
    })
    .then(function(doSomething1Result) {
      console.log('2');
      return doSomething2();
    })
    .then(function(doSomething2Result) {
      console.log('3');
      return doSomething3();
    });
}



foo()
  .then(function(fooResult) {
    console.log(fooResult); // fooResult should be what is returned by doSomething3()
  })
  .catch(function(err) {
    console.error(err); // Can be thrown by any 
  })
  .done(function() {
    console.log('I am always executed! error or success');
  });

If you want to return a promise, in most cases it does not make much sense to use catch (unless you want to recover potential errors). It never make sense to use done in a method returning a promise. You would rather use these methods at the very end of the chain.

Notice that doSomethingX() can return either a value, or a promise, it will work the same.

like image 23
Sebastien Lorber Avatar answered Oct 11 '22 07:10

Sebastien Lorber