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!
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
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
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
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();
Related
I have a factory which is making 'n' number of $http request and to fulfill this I am using $q.all() , so let me show you my factory code.
app.factory('loadBooks',['$http','$q',function($http,$q){
var promises = [];
return {
getBooks : function(category,no){
for(var i =1; i<=no; i++){
var deferred = $q.defer();
console.log(i + 'request');
$http.get(`https://anapioficeandfire.com/api/${category}?page=${i}&pageSize=10`)
.then(function(response){
deferred.resolve(response);
console.log(response);
})
.catch(function(response){
deferred.reject(response);
})
promises.push(deferred);
}
return $q.all(promises);
}
}
}])
And this is my controller from where i am calling the factory function
app.controller('bookController',['$scope','loadBooks','$routeParams',function($scope,loadBooks,$routeParams){
$scope.count = $routeParams.no;
loadBooks.getBooks('books',2).then(function(data){
$scope.books = data;
})
.catch(function(response){
});
}]);
Now the problem is I am making 2 request from the getBooks method and the api is sending me data for both the request but for some reason the first request pushes no data inside the promise array but the second does. I dont know what i am doing wrong. The api is working fine because I can see the data coming in my network tab but data from first request is not getting pushed inside the promise array!
I would be thankful if you could tell me what the problem is. Btw I am new to $q.
You've fallen prey to an insidious bug called "closing over the loop variable". The value of the deferred variable changes in every iteration of the loop, so when this eventually executes:
deferred.resolve(response);
deferred is referring to the last value that the deferred variable took on.
On top of that, you're using the Explicit Promise Creation Antipattern, an antipattern that causes your promise code to be unnecessarily convoluted and error prone.
Solve both problems by using promises the way they're supposed to be used:
app.factory('loadBooks', ['$http', '$q', function($http, $q) {
return {
getBooks: function(category, no) {
var promises = [];
for (var i = 1; i <= no; i++) {
console.log(i + 'request');
promises.push(
$http.get(`https://anapioficeandfire.com/api/${category}?page=${i}&pageSize=10`)
);
}
return $q.all(promises);
}
}
}])
I'm new on AngularJS and JavaScript.
I am getting remote information for each of the elements of an array (cars) and creating a new array (interested prospects). So I need to sync the requests. I need the responses of each request to be added in the new array in the same order of the cars.
I did it first in with a for:
for (a in cars) {
//async request
.then(function () {
//update the new array
});
}
This make all the requests but naturally didn't update the new array.
After seeking in forums, I found this great examples and explanations for returning a intermediate promise and sync all of them.
1. http://pouchdb.com/2015/05/18/we-have-a-problem-with-promises.html
2. http://stackoverflow.com/questions/25605215/return-a-promise-from-inside-a-for-loop
3. http://www.html5rocks.com/en/tutorials/es6/promises/
(#MaurizioIndenmark, #Mark Rajcok , #Michelle Tilley, #Nolan Lawson)
I couldn't use the Promise.resolve() suggested in the second reference. So I had used $q.defer() and resolve(). I guess I have to inject a dependency or something else that I missed. As shown below:
In the Controller I have:
$scope.interestedProspects = [] ;
RequestDetailsOfAsync = function ($scope) {
var deferred = $q.defer();
var id = carLists.map(function (car) {
return car.id;
}).reduce(function (previousValue, currentValue) {
return previousValue.then(function () {
TheService.AsyncRequest(currentValue).then(function (rData) {
$scope.interestedProspects.push(rData);
});
});
}, deferred.resolve());
};
In the Service I have something like:
angular.module('app', []).factory('TheService', function ($http) {
return {
AsyncRequest = function (keyID) {
var deferred = $q.defer();
var promise = authorized.get("somep.provider.api/theService.json?" + keyID).done(function (data) {
deferred.resolve(data);
}).fail(function (err) {
deferred.reject(err);
});
return deferred.promise;
}
}
}
The displayed error I got: Uncaught TypeError: previousValue.then is not a function
I made a jsfiddle reusing others available, so that it could be easier to solve this http://jsfiddle.net/alisatest/pf31g36y/3/. How to wait for AsyncRequests for each element from an array using reduce and promises
I don't know if the mistakes are:
the place where the resolve is placed in the controller function.
the way the reduce function is used
The previousValue and currentValue sometimes are seen by javascript like type of Promise initially and then as a number. In the jsfiddle I have a working example of the use of the reduce and an http request for the example.
Look at this pattern for what you want to do:
cars.reduce(function(promise, car) {
return promise.then(function(){
return TheService.AsyncRequest(car).then(function (rData) {
$scope.details.push(rData);
});
});
}, $q.when());
This will do all the asynchronous calls for every car exactly in the sequence they are in the cars array. $q.all may also be sufficient if the order the async calls are made doesn't matter.
It seems you are calling reduce on an array of ids, but assume in the passed function that you are dealing with promises.
In general, when you want to sync a set of promises, you can use $q.all
You pass an array of promises and get another promise in return that will be resolved with an array of results.
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.
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);
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/
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).
In my controller I am calling a service with the following code:
Service.updateData(data).then(function (result) {
console.log(result);
});
In my service I am using $q to get multiple HTTP requests.
$rootScope.http_1 = $http.get();
$rootScope.http_2 = $http.get();
$q.all([$rootScope.http_1, $rootScope.http_2]).then(function(result) {
console.log(result[0], result[1]);
return result[0], result[1];
});
The code actually works as the http requests are successfully made. However, I get an error in the controller which says: TypeError: Cannot read property 'then' of undefined. I believe this is due the service not returning the promise in the correct way. Any ideas on how to resolve this would be much appreciated?
It looks like you are not returning the promise in updateData Try this:
updateData = function(data) {
$rootScope.http_1 = $http.get();
$rootScope.http_2 = $http.get();
return $q.all([$rootScope.http_1, $rootScope.http_2]);
}
You didn't return the promise, so there is nothing to call .then() on in your controller
You are returning inside the .then() function inside your service.updateData(), which doesn't do very much for you.
If you want to control it all inside the service and return a specific format, try this:
updateData = function(data) {
$rootScope.http_1 = $http.get();
$rootScope.http_2 = $http.get();
var defer = $q.defer();
$q.all([$rootScope.http_1, $rootScope.http_2]).then(function(result){
// process here to get the data how you want it, say in some new var foo
var foo = "some processed data based on result";
defer.resolve(foo);
});
return defer.promise;
}
Hi I am struggling to understand Angular deffering and promises.
I would like to know when all promises have been completed so i can hide a "loading" message.
So here is my code example:
$scope.buildTeam = function () {
$scope.Message = "loading...";
var deferred = $q.defer();
var promiseList = [];
c = $scope.teamModels[0];
for (index = 0; index < c.quantity; ++index) {
var assignment = new teamAssignment(c.assignmentTypeId, $scope.parentAssignment.AssignmentId, c.description);
promiseList.push(departmentService.addAssignment(assignment).then(function (result) {
//added lead/captain. Now insert visually on page
insertToScope($scope, result);
}));
}
}
$q.all(promiseList).then(function () {
deferred.resolve();
$scope.Message = "Loaded.";
});
}
The problem is Have is that $scope.Message shows "Loaded" well before the data has been inserted onto the page in the case of a large data pull.
Could it be that i also need to defer insertToScope?
InsertToScope simply reads:
function insertToScope($scope, a) {
//get root scope
var rs = angular.element("#ngRootContainer").scope();
rs.parentAssig.push(a);
}
And the department service looks like this:
departmentModule.factory('departmentService', function ($http, $q) {
return {
addAssignment: function(assignment){
var deferred = $q.defer();
$http.post('/Department/addAssignmentReturnAssignmentRow', assignment).success(deferred.resolve).error(deferred.reject);
return deferred.promise;
}
});
So my question is, what do i need to do to call a function only after all the promises are done?
Thank you in advance for your feedback.
$q.all should do what you need. If your code doesn't work, I see two options:
You pass in a promiseList that does not contain all promises you want to wait for. Debug your code and check that all promises are passed in. Make sure it's an array you pass as the first parameter.
The promises you pass in resolve before you expect them to resolve. If you log 'A' in insertToScope, and 'B' in the then of your $q.all, you should see
A
...
A
B
in the console.
Here is a simple plunkr that shows how to use $q.all(): http://plnkr.co/edit/9QH9Y0w7DG0WCouMuX82?p=preview