简体   繁体   中英

Angular $q promises and non-linear chaining

Hello everyone out there. I've hit a brick wall. I'm trying to research Angular async method calls, promise objects and implement a solution to a particular issue. After a few hours of trial and error and restructuring my code, I'm certain at this point the answer is right under my nose but I cannot see it.

I have a scenario where I must use $http to make a backend service call, which yields a promise. In the results of promise I will receive a data object with one string property and an array of 100 IDs. This backend service will only deliver payloads of 100 IDs at a time, so I must make x number of $http calls to reach the end of the list, as provided by this backend service. As I understand promises, I must evaluate the $http response in the promise's .then() method and if the string property is 'N' then I know I must call the backend service again for another batch of 100 IDs. After all IDs have been delivered the string will contain a 'Y' indicating that the end of this has been sent and don't call again. No comment required on this scheme, I know it's fairly lame, and unfortunately out of my control. ;-)

Everything I've studied regarding promises just seem to illustrate chaining of promises, in a linear fashion, if more async calls are needed. Whether the promise chain is nested or flat, it seems like I can only make a "known" or fixed number of sequential calls, eg Promise1 to Promise2, to Promise3, etc. Forgive my limited experience with promises here.

Here is a code example, you can see where I'm stuck here, nesting downward. I can't just "hope" after 3 or 5 or 10 calls I'll get all the IDs. I need to dynamically evaluate the result of each call and call again.

Here is the Angular service which invokes $http

(function() {
'use strict';

  angular
      .module('myapp')
      .factory('IDservice', IDservice);

  function IDservice($http) {

    var service = {
    model: {
        error: {
          state: false,
          message: ''
        },
        ids: [],
        end: '',
      },
      GetIDs: function() {
        var request = 'some params...';
        var url = 'the url...';
        return $http.post(url, request).then(reqComplete).catch(reqFailed);

        function reqComplete(response) {
          service.model.ids = response.data.idList;
          service.model.end = response.data.end;
          return service.model;
        }

        function getIntradayMsrFailed(error) {
          service.model.error.state = true;
          service.model.error.message = error;
          return service.model;
        }

      }
    };
    return service;
  }

})();

Here is the Controller which invokes the Angular service, which ultimately drives some UI elements on the respective view:

(function() {
  'use strict';

  angular
    .module('myapp')
    .controller('AvailableIDsController', AvailableIDsController);

  function AvailableIDsController($scope, $location, $timeout, IDservice) {
    var vm = this;
    vm.completeIDList = [];

    activate();    

    function activate() {
        IDservice.GetIDs().then(function(response){
          vm.completeIDList = response.ids;
          console.log('promise1 end');
            if(response.end === 'N'){
              IDservice.GetIDs().then(function(response){
                angular.forEach(response.ids,function(nextID){
                  vm.comleteIDList.push(nextID);
                 });
                console.log('promise2 end'); 
                if(response.end === 'N'){
                  IDservice.GetIDs().then(function(response){
                    angular.forEach(response.ids,function(nextID){
                      vm.comleteIDList.push(nextID);
                    });
                    console.log('promise3 end');
                  });   
                }
             });    
           }
        });         

     console.log('mainthread end');
     }
}
})();

You can see where that's going... and it's very, very ugly.

I need a way, inside the activate() method, to call a method which will take care of invoking the service call and return the result back up to activate(). Now, still in the activate() method, evaluate the result and determine whether to call again, etc. Where I'm stuck is once the main processing thread is done, you're left with program control in that first promise. From there, you can perform another promise, etc. and down the rabbit hole you go. Once I'm in this trap, all is lost. Clearly I'm not doing this right. I'm missing some other simple piece of the puzzle. Any suggestions would be so greatly appreciated!

You're looking for plain old recursion:

function AvailableIDsController($scope, $location, $timeout, IDservice) {
    var vm = this;
    vm.completeIDList = [];
    return activate(0);    

    function activate(i) {
        return IDservice.GetIDs().then(function(response) {
            [].push.apply(vm.completeIDList, response.ids); // simpler than the loop
            console.log('promise'+i+' end');
            if (response.end === 'N'){
                return activate(i+1);
            }
        });
     }
}

Don't forget the return s so that the promises chain together and you can wait for the end of the requests.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM