user1995781
user1995781

Reputation: 19453

Angular promise on multiple $http

I am trying to do multiple $http call and my code looks something like this:

var data = ["data1","data2","data3"..."data10"];

for(var i=0;i<data.length;i++){
    $http.get("http://example.com/"+data[i]).success(function(data){
        console.log("success");
    }).error(function(){
        console.log("error");
    });
}

How can I have the promise to know all $http call is successfull? If anyone of it fail, will perform some action.

Upvotes: 12

Views: 11312

Answers (3)

diegoaguilar
diegoaguilar

Reputation: 8376

You could also use $q.all() method.

So, from your code:

var data = ["data1","data2","data3"..."data10"];

for(var i=0;i<data.length;i++){
    $http.get("http://example.com/"+data[i]).success(function(data){
        console.log("success");
    }).error(function(){
        console.log("error");
    });
}

You could do:

var promises = [];
data.forEach(function(d) {
  promises.push($http.get('/example.com/' + d))
});
$q.all(promises).then(function(results){
  results.forEach(function(data,status,headers,config){
    console.log(data,status,headers,config);
  })
}),

This above basically means execute whole requests and set the behaviour when all have got completed.

On previous comment:

Using status you could get to know if any have gone wrong. Also you could set up a different config for each request if needed (maybe timeouts, for example).

If anyone of it fail, will perform some action.

From docs which are also based on A+ specs:

$q.all(successCallback, errorCallback, notifyCallback);

Upvotes: 14

Dan
Dan

Reputation: 10538

Accepted answer is okay, but is still a bit ugly. You have an array of things you want to send.. instead of using a for loop, why not use Array.prototype.map?

var data = ["data1","data2","data3"..."data10"];

for(var i=0;i<data.length;i++){
    $http.get("http://example.com/"+data[i]).success(function(data){
        console.log("success");
    }).error(function(){
        console.log("error");
    });
}

This becomes

var data = ['data1', 'data2', 'data3', ...., 'data10']
var promises = data.map(function(datum) {
  return $http.get('http://example.com/' + datum)
})
var taskCompletion = $q.all(promises)
// Usually, you would want to return taskCompletion at this point,
// but for sake of example

taskCompletion.then(function(responses) {
  responses.forEach(function(response) {
    console.log(response)
  })
})

This uses a higher order function so you don't have to use a for loop, looks a lot easier on the eyes as well. Otherwise, it behaves the same as the other examples posted, so this is a purely aesthetical change.

One word of warning on success vs error - success and error are more like callbacks and are warnings that you don't know how a promise works / aren't using it correctly. Promises then and catch will chain and return a new promise encapsulating the chain thus far, which is very beneficial. In addition, using success and error (anywhere else other than the call site of $http) is a smell, because it means you're relying explicitly on a Angular HTTP promise rather than any A+ compliant promise.

In other words, try not to use success/error - there is rarely a reason for them and they almost always indicate a code smell because they introduce side effects.


With regards to your comment:

I have did my own very simple experiment on $q.all. But it only trigger when all request is success. If one if it fail, nothing happen.

This is because the contract of all is that it either resolves if every promise was a success, or rejects if at least one was a failure.

Unfortunately, Angular's built in $q service only has all; if you want to have rejected promises not cause the resultant promise to reject, then you will need to use allSettled, which is present in most major promise libraries (like Bluebird and the original Q by kriskowal). The other alternative is to roll your own (but I would suggest Bluebird).

Upvotes: 5

Enkode
Enkode

Reputation: 4783

If you are looking to break out on the first error then you need to make your for loop synchronous like here: Angular synchronous http loop to update progress bar

var data = ["data1", "data2", "data3", "data10"];
$scope.doneLoading = false;
var promise = $q.all(null);

angular.forEach(data, function(url){
  promise = promise.then(function(){
    return $http.get("http://example.com/" + data[i])
      .then(function (response) {
        $scope.data = response.data;
      })
      .catch(function (response) {
        $scope.error = response.status;
      });
  });
});

promise.then(function(){
  //This is run after all of your HTTP requests are done
  $scope.doneLoading = true;
});

If you want it to be asynchronous then: How to bundle Angular $http.get() calls?

app.controller("AppCtrl", function ($scope, $http, $q) {
  var data = ["data1", "data2", "data3", "data10"];
  $q.all([
    for(var i = 0;i < data.length;i++) {
      $http.get("http://example.com/" + data[i])
        .then(function (response) {
          $scope.data= response.data;
        })
        .catch(function (response) {
          console.error('dataerror', response.status, response.data);
          break;
        })
        .finally(function () {
          console.log("finally finished data");
        });
    }
  ]).
  then(function (results) {
    /* your logic here */
  });
};

This article is pretty good as well: http://chariotsolutions.com/blog/post/angularjs-corner-using-promises-q-handle-asynchronous-calls/

Upvotes: 7

Related Questions