0

Am looping over an array and making async calls to an api the returned data need to me merged with a different array the problem am facing when the merge occurs some of the promises have not being resolved yet so the resulting array after the merge is missing data . any ideas how to go about this . (am new to Angular). the array am looping through has at least 200 elements (I don't know the size of the array in advance) i get the id from each element and i call this service:

for (i = 0; i < list.length; i++) {
     service.getRawHtml(list[i].service_object_id)
        .then(function(res){
            temp=htmlToArray(res);
            htmlDataList.push(temp);
    }, function(err){
         // error
    })
    }



service.getRawHtml=function (id){ 
   var defer = $q.defer();
        $http({
            method: 'GET',
            url: 'http://wlhost:50000/'+id
        }).success(function (response) {
            defer.resolve(response);
        }).error(function (data, status) {
            console.log("Failure");
            defer.reject(err);
    });
    return defer.promise;
}

Thanks in advance.

Mero
  • 622
  • 12
  • 24
  • Possible duplicate of [Wait for all promises to resolve](http://stackoverflow.com/questions/21759361/wait-for-all-promises-to-resolve) – Daniel B Jan 10 '17 at 14:42
  • I had a look at the question before answering mine , the answer that was proposed for my question is a similar approach. but it's not the same scenario am facing :) – Mero Jan 10 '17 at 14:44
  • Your code doesn't have any loop..? If it's not the *"same scenario your facing"*, perhaps you should include more of your code to illustrate what it is you're actually have problem with. – Daniel B Jan 10 '17 at 14:50
  • i didn't think it was needed to put it , just a for loop calling the service above with different ids. the size of the array varies . do you see that it is not like the other question ? – Mero Jan 10 '17 at 14:53
  • It's probably *very* much needed since that's how you call your promise returning funciton. Based on your description, you've written your code as if it were synchronous, which causes the error. I'm 99% sure that `$q.all()` will solve but it's impossible to know unless you provide the code from which you call the `getRawHtml()` function. – Daniel B Jan 10 '17 at 14:56
  • @DanielB any ideas ? do you still think it's a duplicate if not can you please remove your first comment . Thanks – Mero Jan 10 '17 at 15:18
  • 1
    It *is* a duplicate of that question, but I have added my own answer to this now where I also included a fix for the so called [deferred anti-pattern](https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns#the-deferred-anti-pattern) that you've implemented. – Daniel B Jan 10 '17 at 15:29
  • Thanks for your help – Mero Jan 10 '17 at 15:31
  • 1
    You're welcome! For future reference, it's better to include too much code than too little! ;) – Daniel B Jan 10 '17 at 15:33
  • am still having an issue with the '$q' scope it's undefined the for loop is in a chain of promises , i will try to figure it out – Mero Jan 10 '17 at 16:43
  • Is `$q` injected properly? – Daniel B Jan 10 '17 at 17:13
  • it's my first time using angular , i don't think it is injected properly . i have injected in my controller and i pass it to the method that contains the loop but still undefined – Mero Jan 10 '17 at 17:17

3 Answers3

4

Use $q.all - From the documentation:

Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.

Example usage:

    $q.all([
        getPromise(),
        getPromise(),
        getPromise() 
    ]).then(function(value) {
        $scope.result1 = value[0];
        $scope.result2 = value[1];
        $scope.result3 = value[2];
    }, function(reason) {
        $scope.result = reason;
    });
ndoes
  • 667
  • 5
  • 16
  • i just updated my question i don't think the solution you suggested might work in my case but i will give it a shot – Mero Jan 10 '17 at 14:27
2

just to clerify @nodes solution:

var promisesToWaitFor = [];
for (i = 0; i < list.length; i++) {
    var promis = service.getRawHtml(list[i].service_object_id)
    promisesToWaitFor.push(promis)
}
$q.all(promisesToWaitFor).then(function(resultsArray) {
    var flatten = [].concat.apply([],resultsArray);
    temp=htmlToArray(flatten);
    htmlDataList.push(temp);
}, function(err) {
    // error
});
Mortalus
  • 10,574
  • 11
  • 67
  • 117
2

As @ndoes suggests, this can be done with $q.all(), which takes an array of promises and resolves when all of them are resolved, or rejects if any of them is rejected.

You're calling the asynchronous function inside the for loop which will execute, but as soon as all of the function calls have been made, the code will continue synchronously. There wont be any magical awaiting for all the promises to resolve.

What you instead should do is to push all of the returning promises to an array, then use $q.all() to await them all.

//Declare an array to which we push the promises
var promises = [];

for (i = 0; i < list.length; i++) {
    //Call the service and push the returned promise to the array
    promises.push(service.getRawHtml(list[i].service_object_id));
}

//Await all promises
$q.all(promises)
    .then(function(arrayOfResults){
        //Use your result which will be an array of each response after
        //it's been handled by the htmlToArray function
    })
    .catch(function(err){
        //Handle errors
    });

I took the opportunity to refactor your code at the same time, as it is implementing the deferred anti-pattern. Since $http already returns a promise, there's no need to create and return a new one. Simply return it right away like

service.getRawHtml = function (id){ 
   return $http({
            method: 'GET',
            url: 'http://wlhost:50000/'+id
        }).success(function (response) {
            return htmlToArray(response);
        }).error(function (data, status) {
            console.log("Failure");
            $q.reject(error);
    });
}
Daniel B
  • 8,770
  • 5
  • 43
  • 76