26

I have a service called paymentStrategy that get injected in my controller.

$scope.buy = function() {
  paymentStrategy.buy()
    .then(function(response) {

  }
}

This buy method from paymentStrategy triggers several methods that needs to be called sequentially. When all the methods within buy() are done, then() needs to be called.

It is probably trivial but I am quite new to angular.

At the moment, buy().then() gets triggered straight after the init() methods. I have the feeling we need to put all theses methods in a array of promises and apply $q.all().

Any help or suggestion would be greatly appreciated

angular.module('deps-app.payment.services', []).
  factory('paymentStrategy', function($q) {

 var deferred = $q.defer();
 var ITEM_TO_PURCHASE = "test.beer.managed";
 var promises = [];

 var handlerSuccess = function(result) {
      deferred.resolve(result);
  };

 var handlerError = function(result) {
      deferred.reject(result);
  };

 _init = function() {

     inappbilling.init(handlerSuccess, handlerError, { showLog:true }); 
     return deferred.promise;
    }

  _purchase = function() {
        inappbilling.buy(handlerSuccess, handlerError, ITEM_TO_PURCHASE);
        return deferred.promise;
  }

  _consume = function() {
        inappbilling.consumePurchase(handlerSuccess, handlerError, ITEM_TO_PURCHASE);
        return deferred.promise;
  }

return  {

     buy: function() {

      _init();
        .then(_purchase());
        .then(_consume());  

      return deferred.promise;                    
    }

 }
});
Florent Valdelievre
  • 1,546
  • 3
  • 20
  • 32
  • 1
    Do all he methods on inappbilling such as init, buy and consumePurchase return promise? – Chandermani Jun 23 '14 at 01:59
  • Sounds good but can you please clarify the way to do it ? – Florent Valdelievre Jun 23 '14 at 02:18
  • That was a question to you Florent. The problem with your code is that you resolve the promise on init callback and on other callbacks too, but you need to wait for all calls to finish before calling resolve. – Chandermani Jun 23 '14 at 02:48
  • The 2 first parameters handlerSuccess and handlerError are callbacks that gets called when the methods (init, buy and consumePurchase) are finished. i.e, _purchase() needs to be called only if handlerSuccess from init(...) has been called. _consume needs to be called only if handlerSuccess from purchase(...) has been called. – Florent Valdelievre Jun 23 '14 at 03:02

2 Answers2

57

If you need to chain promises in Angular sequentially, you can simply return the promises from one to another:

callFirst()
.then(function(firstResult){
   return callSecond();
})
.then(function(secondResult){
   return callThird();
})
.then(function(thirdResult){
   //Finally do something with promise, or even return this
});

And if you want to return all of this as an API:

function myMethod(){
   //Return the promise of the entire chain
   return first()
           .then(function(){
               return second();
           }).promise;
}
Josh
  • 44,706
  • 7
  • 102
  • 124
  • I'm new to promise and for angular also. here the first then return calls second then but it is returning method , actually second then takes the value as argument right.I know only C so I'm not sure. – santhosh Dec 25 '15 at 11:06
  • 1
    So simple and so powerful! Thank you. I understood what I have to do, only after I read your post. All other examples I found were so complicated. – tarekahf Dec 22 '16 at 16:20
  • This is the right way. The 'return' is required otherwise this won't work. – Leo Huang May 01 '18 at 19:42
20

Make all methods atomar, by adding their own promises. In your code, the first resolve will complete the whole request.

If the methods have their own promise, you can chain them with ease.

angular.module('deps-app.payment.services', []).factory('paymentStrategy', function($q) {
var ITEM_TO_PURCHASE = "test.beer.managed";

_init = function() {
  return $q(function (resolve, reject) {
    inappbilling.init(resolve, reject, { showLog: true }); 
  });
};

_purchase = function() {
  return $q(function (resolve, reject) {
    inappbilling.buy(resolve, reject, ITEM_TO_PURCHASE);  
  });
};

_consume = function() {
  return $q(function (resolve, reject) {
    inappbilling.consumePurchase(resolve, reject, ITEM_TO_PURCHASE);
  });
};

return  {
  // In this case, you don't need to define a additional promise, 
  // because placing a return in front of the _init, will already return 
  // the promise of _consume.
  buy: function() {    
    return _init()
      .then(_purchase)  
      // remove () from inside the callback, to pass the actual method 
      // instead the result of the invoked method.
      .then(_consume);      
  }    
};

});

Konstantin Krass
  • 8,626
  • 1
  • 18
  • 24
  • Thanks you very much for your help, much appreciated. One question before accepting it. It seems that it never goes in then(..) in buy() method. Is it a good way to do it that way? : _init() .then(_consumeAll) .then(_purchase) .then(_consume) .then(deferred.resolve) – Florent Valdelievre Jun 23 '14 at 15:30
  • ah my mistake. check now – Konstantin Krass Jun 24 '14 at 05:10
  • 1
    Using `$q.defer()` is [_almost always_ bad practice](http://www.codelord.net/2015/09/24/$q-dot-defer-youre-doing-it-wrong/), and this is no exception. Each of the three functions could be simplified by doing something like `_init = function() { return $q(function(resolve, reject) { inappbilling.init(resolve, reject, { showLog: true }); }); };` which will return a promise as desired. Ideally, the methods on `inappbilling` should return promises (which are simply returned by your _init, etc methods), but I suppose that may not be your decision to make. – Sean the Bean Aug 08 '16 at 21:30
  • @Sean the Bean is right. @ 2014 this was the only correct answer for me, until i discovered the `return $q.` routine. – Konstantin Krass Aug 09 '16 at 07:32