7

Im trying to iterate over an array that I construct from multiple http calls inside a angular.forEach()

the function

$scope.ticket_stats = function(){
    //cleaning variables
    $scope.data_set = [];
    $scope.closed_tickets = [];

    //fetching time stamps (epoch)
    $scope.time_frame = time_period.days(7);

              //calling data using time stamps
              angular.forEach($scope.time_frame, function(item) {
                  //debug
                  console.log(item);

                  var promise = tickets.status("closed", item);

                  promise.success(function(data){
                      console.log(data);
                      $scope.closed_tickets.push(data[0].datapoints[0][0]); // returns a numerical value
                  });

              });
              //SEE MESSAGE BELOW
              $scope.data_set.push($scope.closed_tickets);

}

the last line $scope.data_set.push() is working but increment itself over time once calls return data. I would like this line to be executed once everything within the for Each loop is all done. I need to iterate over the $scope.closed_tickets array afteward to play (addition) data inside it and build up a second array.

here are the services used in this function:

// CALL TICKETS STATS
app.service('tickets', function($http){

    this.status = function(status, date){
        var one_snap = date - 100;
        var url = "/url/render?format=json&target=sum(stats.tickets."+status+")&from="+one_snap+"&until="+date+"";
        return $http.get(url);
    };            
});

// TIME STAMPS MATHS
app.service('time_period', function(){
    var currentDate = parseInt((new Date).getTime()/1000);

    this.days = function(number){
        var pending = [];
        for (var i = number; i > 0; i--) {
            pending.push(currentDate - (87677*i));
        }
        return pending;
    }; 
});

I search for information and found out about the $q.all() service but didn't manage to make this work the way I want.

Any advices would be welcomed! Thanks!

3
  • have a look a async processing libs such as async.js Commented Apr 1, 2015 at 19:33
  • this is similar to using the $q service. This is just that I don't see it working. A more detailed answer would be welcomed. Commented Apr 1, 2015 at 19:43
  • Not really, $q.all will be invoked when all promises are resolved, but they will be executed in parallel. async could help you sequentialize them, or you could call the next one once the previous one has finished using $q.when Commented Apr 1, 2015 at 20:56

3 Answers 3

22

You can use $q.all to wait for multiple ansynchronous events (promises) to finish.

$scope.ticket_stats = function() {
    // list of all promises
    var promises = [];

    //cleaning variables
    $scope.data_set = [];
    $scope.closed_tickets = [];

    //fetching time stamps (epoch)
    $scope.time_frame = time_period.days(7);

    //calling data using time stamps
    angular.forEach($scope.time_frame, function(item) {
        // create a $q deferred promise
        var deferred = $q.defer();
        //debug
        console.log(item);

        tickets.status("closed", item).success(function(data) {
            console.log(data);
            $scope.closed_tickets.push(data[0].datapoints[0][0]);

            // promise successfully resolved
            deferred.resolve(data);
        });

        // add to the list of promises
        promises.push(deferred.promise);
    });

    // execute all the promises and do something with the results
    $q.all(promises).then(
        // success
        // results: an array of data objects from each deferred.resolve(data) call
        function(results) {
            $scope.data_set.push($scope.closed_tickets);
        },
        // error
        function(response) {
        }
    );
}

First, deferred represents a piece of code that will take an unknown amount of time to execute (asynchronous). deferred.resolve(data) simply states that the code is finished. Data could be anything, an object, string, whatever, but it is usually the results of your asynchronous code. Likewise you can reject a promise with deferred.reject(data) (maybe an error was thrown by the sever). Again, data can be anything but here it should probably be the error response.

deferred.promise just returns a promise object. The promise object allows you to set callbacks like .then(successFunction, errorFunction) so you know a piece of code has finished executing before moving on to successFunction (or errorFunction in the case of a failure). In our case $q has the .all method which waits for an array of promises to finish then gives you the results of all the promises as an array.

Don't forget to inject the $q service.

Sign up to request clarification or add additional context in comments.

2 Comments

ok so just to make sure, let say I have two calls to make instead of one (eg: tickets.status('closed'). I've just need to duplicate this bit and use the same deferred.resolve(data) inside it ? (following your example.
That depends on if call 2 happens after call 1 or at the same time. If call 1 happens and then call 2 happens (call 2 is nested inside the success of call 1) you would just move deferred.resolve inside of call2.success. Otherwise you would have $q.all([call1, call2]) inside your forEach loop. Where call1 and call2 would each have a deferred object and resolve call.
1

Try to make an array of promises only, without resolving them yet. Then aggregate them with $q.all(). After aggregated promise resolve iterate through the array of those promises again. Now you are sure that they are all resolved.

var promises = [];
angular.forEach($scope.time_frame, function(item) {
    promises.push(tickets.status("closed", item));
});

var aggregatedPromise = $q.all(promises);

aggregatedPromise.success(function(){
    angular.forEach(promises, function(promise) {
        promise.success(function(data){
            $scope.closed_tickets.push(data[0].datapoints[0][0]); // returns a numerical value
        });
    });
});

Maybe this is not the most efficient way to do this, but I think that should solve your problem.

Comments

1

Even though you mention $q.all didn't work for you, as I don't see why it should't, here's how I would do this.

Basically you want to map an array of things (time stamps) to some other things (promises in this case since we have async calls) and do some action on the resulting array.

var promises = $scope.time_frame.map(function (item) {
    return tickets.status("closed", item);
});

Now we use $q.all to wait for all promises to resolve:

$q.all(promises).then(function (tickets) {
    $scope.data_set.push(tickets);
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.