How to make synchronous AJAX requests in AngularJS controller

19.6k views Asked by At

I've been told that $http in Angular is asynchronous. However, for some purpose, I need to make sequential AJAX requests. I want to read all the files from a file list, and then get the number from all those files. For example:

content of "fileNames":

file1
file2

content of "file1":

1

content of "file2":

2

The following code will calculate the sum

<!DOCTYPE html>
<html>
<body>
<p id="id01"></p>
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script>

var fileString;
/* first AJAX call */
$.ajax({
    url: 'fileNames', type: 'get', async: false,
    success: function(content) {
        fileString = content;
    }
});
var fileList = fileString.split('\n');
var sum = 0;
for (var i = 0; i < fileList.length; i++) {
      /* second AJAX call in getNumber function */
      sum += getNumber(fileList[i]);
}
document.getElementById("id01").innerHTML = sum;

function getNumber(file) {
    var num;
    $.ajax({url: file, type: 'get', async: false,
      success: function(content) {
            num = content;
        }
    });
    return parseInt(num);
}

</script>
</body>
</html>

Since the two $.ajax calls are sequential, I don't know how to achieve this functionality in AngularJS. Say, in the end, I want $scope.sum = 1 + 2.

Can someone make it work in AngularJS? Some simple code would be appreciated!

4

There are 4 answers

3
PSL On BEST ANSWER

You could make use of promises and promise chaining (with $q and the promise returned by $http). Example: In your controller you could do (after injecting $http, $q):

angular.module('myApp').controller('MyCtrl', ['$http','$q','$scope', function($http, $q, $scope){

    function getData(){
        //return promise from initial call
         return $http.get('fileNames')
                .then(processFile) //once that is done call to process each file
                .then(calculateSum);// return sum calculation
      }

      function processFile(response){
         var fileList = response.data.split('\n');
          //Use $q.all to catch all fulfill array of promises
          return $q.all(fileList.map(function(file){
             return getNumber(file);
          }));
      }

      function getNumber(file) {
          //return promise of the specific file response and converting its value to int
          return $http.get(file).then(function(response){
             return parseInt(response.data, 10);
          });

          //if the call fails may be you want to return 0? then use below
          /* return $http.get(file).then(function(response){
             return parseInt(response.data, 10);
          },function(){ return 0 });*/
      }

      function calculateSum(arrNum){
          return arrNum.reduce(function(n1,n2){
             return n1 + n2;
          });
      }

      getData().then(function(sum){
         $scope.sum = sum;
      }).catch(function(){
         //Oops one of more file load call failed
      });

}]);

Also see:

  • $http
  • $q
  • $q.all
  • Array.map - You could as well manually loop though the array and push promise to array.
  • Array.reduce - You could as well loop through number and add them up.

This does not mean that the calls are synchronous, but they are asynchronous and still does what you need in a more efficient way and easy to manage.

Demo

0
n00b On

The other answers show how you properly use $http in an Asynchronous manner using promises or what is also called chaining, and that is the correct way of using $http. Trying to do that Synchronously as you have asked will block the Controller's cycle which is something you never want to do.

Still you can do the terrible thing of checking status of a promise in a loop. That can be done by the $$state property of the promise which has a property named status

0
Patrick Evans On

You can use the promise that is returned by $http method calls:

//Do some request
$http.get("someurl")
//on resolve call processSomeUrlResponse
.then(processSomeUrlResponse)
//then do another request
.then(function(){
   return $http.get("anotherurl").then(processAnotherUrlResponse);
})
//when previous request is resolved then do another 
.then(function(){
   return $http.get("yetanotherurl").then(processYetAnotherUrlResponse);
})
//and do another
.then(function(){
   return $http.get("urls").then(processUrlResponse);
});

When you return a promise in a then callback the next then will not be called till the promise has been resolved.

Angular's $q(deferred/promise) service

1
Sir Neuman On

It is possible with angular http functions using promises. E.G:

$scope.functionA = function(){
    return $q(function(resolve){
        resolve("theAnswertoallquestions");
    });
}

$scope.functionB = function(A){
    return $q(function(resolve);
        $http.get("URLRoot/" + A).success(function(){resolve();});
    });
}

$scope.functionC = function(){
    return $q(function(resolve);
        resolve("I AM THE LAST TO EXEGGCUTE!!!");
    });
}

$scope.allTogetherNow = function(){
    var promise = $scope.functionA();
    promise.then(function(A){
        return $scope.functionB(A);
    }).then(function(){
        return $scope.functionC();
    }).then(function(){ 
        return "ALL DONE"
    });
}

$scope.allTogetherNow();