Angular promise on multiple $http
Asked Answered
H

3

12

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.

Hummocky answered 26/9, 2015 at 6:30 Comment(4)
You'll likely want to look into the $q service and specifcally $q.all.Kanzu
@Kanzu 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.Hummocky
If one if it fail, nothing happen - you're using .all wrong in that caseIdellaidelle
@Hummocky the reason this occurs is because that is the expected behaviour of all. If you want to have the aggregate promise resolve even if a child promise is rejected, you will need to look into using allSettled. Unfortunately, this method isn't in Angular's $q library, but it is in the big-name libraries for Promises, like Bluebird.Esmond
O
14

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);
Ogrady answered 26/9, 2015 at 7:17 Comment(4)
Thanks for your answer. Tried with your code. If one fail, it does not execute the code inside $q.all(promises).then(function(results){Hummocky
Which is the one failing? Could you share a bit more of your code?Ogrady
I mean if any one of the $http call fail (that return 404 error, for example), it will not execute the $q.all() code. I am now working on experiment code. So the code is as clean as your provided answer.Hummocky
Please check my last edit, add a second callback, even the third one as you're experimenting. Angular will interprete any status code from 200 to 299 as success and the rest as error, so you might use the errorCallback with same parametersOgrady
C
7

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/

Chayachayote answered 26/9, 2015 at 6:54 Comment(6)
Thanks for your answer. I have tried with your solution, but it did nothing when one of it fail. How can I get respond when one of it fail?Hummocky
I added updated the answer, try using .then, .catch, .finally instead of .success, .error peterbe.com/plog/promises-with-$httpChayachayote
If that still doesn't work, can you just break the for loop on the .catch or errorChayachayote
I added a synchronous scenario. Is that what you really are asking for a loop that will fail on first break?Chayachayote
Please consider editing your post to add more explanation about what your code does and why it will solve the problem. An answer that mostly just contains code (even if it's working) usually wont help the OP to understand their problem.Chilson
Your second example would not work even if it were syntactically correct.Kaiserdom
E
5

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).

Esmond answered 26/9, 2015 at 12:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.