I have implemented the $q.all in angularjs, but I can not make the code work. Here is my code :
UploadService.uploadQuestion = function(questions){
var promises = [];
for(var i = 0 ; i < questions.length ; i++){
var deffered = $q.defer();
var question = questions[i];
$http({
url : 'upload/question',
method: 'POST',
data : question
}).
success(function(data){
deffered.resolve(data);
}).
error(function(error){
deffered.reject();
});
promises.push(deffered.promise);
}
return $q.all(promises);
}
And here is my controller which call the services:
uploadService.uploadQuestion(questions).then(function(datas){
//the datas can not be retrieved although the server has responded
},
function(errors){
//errors can not be retrieved also
})
I think there is some problem setting up $q.all in my service.
In javascript there are no block-level scopes only function-level scopes:
Read this article about javaScript Scoping and Hoisting.
See how I debugged your code:
var deferred = $q.defer();
deferred.count = i;
console.log(deferred.count); // 0,1,2,3,4,5 --< all deferred objects
// some code
.success(function(data){
console.log(deferred.count); // 5,5,5,5,5,5 --< only the last deferred object
deferred.resolve(data);
})
When you write var deferred= $q.defer(); inside a for loop it's hoisted to the top of the function, it means that javascript declares this variable on the function scope outside of the for loop.
With each loop, the last deferred is overriding the previous one, there is no block-level scope to save a reference to that object.
When asynchronous callbacks (success / error) are invoked, they reference only the last deferred object and only it gets resolved, so $q.all is never resolved because it still waits for other deferred objects.
What you need is to create an anonymous function for each item you iterate.
Since functions do have scopes, the reference to the deferred objects are preserved in a closure scope even after functions are executed.
As #dfsq commented: There is no need to manually construct a new deferred object since $http itself returns a promise.
Solution with angular.forEach:
Here is a demo plunker: http://plnkr.co/edit/NGMp4ycmaCqVOmgohN53?p=preview
UploadService.uploadQuestion = function(questions){
var promises = [];
angular.forEach(questions , function(question) {
var promise = $http({
url : 'upload/question',
method: 'POST',
data : question
});
promises.push(promise);
});
return $q.all(promises);
}
My favorite way is to use Array#map:
Here is a demo plunker: http://plnkr.co/edit/KYeTWUyxJR4mlU77svw9?p=preview
UploadService.uploadQuestion = function(questions){
var promises = questions.map(function(question) {
return $http({
url : 'upload/question',
method: 'POST',
data : question
});
});
return $q.all(promises);
}
$http is a promise too, you can make it simpler:
return $q.all(tasks.map(function(d){
return $http.post('upload/tasks',d).then(someProcessCallback, onErrorCallback);
}));
The issue seems to be that you are adding the deffered.promise when deffered is itself the promise you should be adding:
Try changing to promises.push(deffered); so you don't add the unwrapped promise to the array.
UploadService.uploadQuestion = function(questions){
var promises = [];
for(var i = 0 ; i < questions.length ; i++){
var deffered = $q.defer();
var question = questions[i];
$http({
url : 'upload/question',
method: 'POST',
data : question
}).
success(function(data){
deffered.resolve(data);
}).
error(function(error){
deffered.reject();
});
promises.push(deffered);
}
return $q.all(promises);
}
Related
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.
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;
});
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);
});
}
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
});
Controller.js:
service.getUsers(params).$promise.then(function(response) {
var data = response;
$scope.users = data.resources;
$scope.totalItems = data.totalResults;
});
Service.js:
return $resource('/Users', {}, {
getUsers: {
method: 'GET',
headers: {'segment' : 'computedSegment'},
isArray: true,
transformResponse: function(data, header){
var wrapped = angular.fromJson(data);
angular.forEach(wrapped.resources, function(item, idx) {
wrapped.resources[idx] = item;
});
var deffered = $q.defer();
deffered.resolve(wrapped);
return deffered.promise;
}
}});
The issue i am getting, is that although the request goes to the server, i receive a response, the data is correctly processed, the ,,then" function will never be executed to set the $scope variables.
Does anyone have an idea?
I guess you forgot to use query() method after getUsers method. And I think you don't need to use promise in transformResponse method. You can check this answers.
I have a factory where I'm getting data from a server using the $http methods:
.factory('$factory', function ($q, $http, $timeout, $state, $ionicHistory, $localstorage) {
var obj = [];
var functions = {
getData: function () {
var dfd = $q.defer();
if(!obj){
$http({
url: remoteUrl+'/getdata',
method: 'POST',
data: {}
}).then(function(response) {
$timeout(function(){
obj = response.data;
dfd.resolve(response.data);
}, 2000)
}, function(response) {
}
}else{
return obj;
}
return dfd.promise;
}
}
}
So this gets the data and puts it in an object. Correct me if I'm wrong, but this method of using a factory for this type of action is so it's not tied to a controller, and can be used anywhere in my application.
With this in mind, I wish to make it so I can get the data anywhere in my app without having to query the server each time. i.e. once it's queried the server the factory saves the response to an object (like I'm doing now). But I'm having trouble accessing the data afterwards in another controller for example.
I've started to make what I think it should look like by using the if(!obj) line, but in the else statement I cant seem to just return the obj object. It throws errors as it's not returning a promise like it's expected too.
I'm not sure if I'm even along the right lines with this.
Thanks
You're returning a promise so you need to always return a promise even on the cached response, you can do it relatively easy at this level by wrapping it in a $q.when (https://docs.angularjs.org/api/ng/service/$q) which will return a promise immediately resolved.
return $q.when(obj);
Although $http service has built in caching, so you may want to take a look under the Cache section.
https://docs.angularjs.org/api/ng/service/$http
https://www.ng-book.com/p/Caching/
This should work:
Insteada assigning obj = [] assign as a null. Usually I prefer callback. You can try this code:
.factory('$factory', function($q, $http, $timeout, $state, $ionicHistory, $localstorage) {
var obj = [];
var functions = {
getData: function(cb) {
// instead of checking !obj you have to check for length or you have to set obj as null
if (obj && obj.length == 0) {
$http({
url: remoteUrl + '/getdata',
method: 'POST',
data: {}
}).then(function(response) {
obj = response.data;
cb(response.data)
}, function(response) {
}
}
else {
cb(obj)
}
}
}
}
})
// You can use callback by following code
$factory.getData(function(response){
// response will come here
})
I'm trying to run a sequence (one at at time, not in parallel) of promises for http GETs. Think of submitting a cooking recipe and the server will only accept one step per HTTP request. And they have to be sent in order or the pie will be a mess. And the last step is the only one returning data (a hot apple pie).
I have created a Plunker http://plnkr.co/edit/yOJhhw?p=preview
We'll start with the controller
angular.module("app")
.controller("sequencialCtrl", ["$scope", "seq", function($scope, seq) {
The controller starts with a list of steps represented by library files so they will take >0 time to d/l.
var recipeSteps = [
'http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js',
'https://ajax.googleapis.com/ajax/libs/angularjs/1.3.15/angular.min.js',
'https://ajax.googleapis.com/ajax/libs/angular_material/1.0.0/angular-material.min.js'
];
Then I create a inters scope variable to display the first 80 chars of the retrieved js files for testing purposes only.
$scope.inters = seq.intermediates;
Then I try to call the factory provided promise. This crashes in the console at the then
// the console shows that seq.submitRecipe is undefined
seq.submitRecipe.then(function(data) {
$scope.results = data;
});
}]);
Now the Factory
angular.module('app').factory('seq', function($http, $q) {
and we create intermediates and submitRecipe
var intermediates = ['test1', 'test2'];
var submitRecipe = function(theSteps) {
var deferredRecipe = $q.defer();
var deferredPromise = deferredRecipe.promise;
deferredPromise
.then(function() {
var deferred = $q.defer();
var promise = deferred.promise;
$http({
method: 'GET',
url: theSteps.shift()
}).then(function(response) {
intermediates.push( response.data.substr(0, 80) );
deferred.resolve(response.data.substr(0, 80));
});
return promise;
})
.then(function() {
var deferred = $q.defer();
var promise = deferred.promise;
$http({
method: 'GET',
url: theSteps.shift()
}).then(function(response) {
intermediates.push( response.data.substr(0, 80) );
deferred.resolve(response.data.substr(0, 80));
});
return promise;
})
.then(function() {
var deferred = $q.defer();
var promise = deferred.promise;
$http({
method: 'GET',
url: theSteps.shift()
}).then(function(response) {
intermediates.push( response.data.substr(0, 80) );
deferred.resolve( "Apple Pie");
});
return promise;
});
As noted before I only want to return the data from that last then which is "Apple Pie".
We close out the submitRecipefunction with...
// if this resolve isnt here, nothing is execute
deferredRecipe.resolve();
$rootScope.$apply();
return deferredPromise;
};
I have found that if I dont have that resolve() the thens aren't run.
And finally we expose our factory's methods.
return {
submitRecipe: submitRecipe,
intermediates: intermediates
};
});
At the end of the day I would like $scope.results to be "Apple Pie".
Appreciate any help.
Here is the working plunkr
There were a couple of edits which had to be made:
submitRecipe is a function, so you call it in this way:
seq.submitRecipe(recipeSteps).then(function(data) {
$scope.results = data;
});
Then you may remove the unnecessary $rootScope.$apply():
deferredRecipe.resolve();
// $rootScope.$apply();
return deferredPromise;
I am having a service which is calling two other async services and returns some combined data, but how can this be done in AngularJS.
I have a controller in which I would like to call these two methods similar to this:
function ServiceC(serviceA,serviceB) {
var dataA = serviceA.GetAsyncStuff(); //returns promise
var dataB = serviceB.GetAsyncStuff(); //returns promise
return 'Greetings ' + dataA + dataB;
}
Is there a smarter way than to actually nest the Then method calls like this:
function ServiceC(serviceA,serviceB) {
var dataA = serviceA.GetAsyncStuff().then(function(respA){
var dataB = serviceB.GetAsyncStuff().then(function(respB){
return 'Greetings ' + respA.data + respB.data;
});
});
}
This example is of course a bit simplified.
Here is a demo plunker: http://plnkr.co/edit/yCrbwnarVDqwC4GBxhGg?p=preview
You need to use $q.all:
app.factory('myService', function($q, serviceA, serviceB, serviceC){
// $http returns a response object so we need to extract the data
var fn = function(res){
return res.data;
};
var promises = [
serviceA.GetAsyncStuff().then(fn),
serviceB.GetAsyncStuff().then(fn)
];
return $q.all(promises).then(function(data){
// you can manipulate consolidated data here
// data.push(serviceC);
return data;
});
});
From $q docs:
$q#all(promises);
Combines multiple promises into a single promise that is resolved when all of the input promises are resolved.
Returns
Returns a single promise that will be resolved with an array/hash of values, each value corresponding to the promise at the same index/key in the promises array/hash. If any of the promises is resolved with a rejection, this resulting promise will be rejected with the same rejection value.
function ServiceC(serviceA,serviceB) {
var _totalData = {};
var dataA = serviceA.GetAsyncStuff().then(function(respA){
_totalData = respA;
});
var dataB = serviceB.GetAsyncStuff().then(function(respB){
_totalData += respB;
});
return{ totalData: _totalData}
}
I say first one is better as it is having two different calls and you can add your data and return it at the end and and in your second example you using service under service that would creating a overhead for saving the value of one service till 2nd service get called and call its data, you can see the network pressure second one is taking much time to get the resources and to return the resources.
We can call both services and return promise.
myApp.provider("myService", function($http,$q){
var serviceOneData = {},
serviceTwoData = {};
//call service 1
return {
serviceOneData : serviceOneData ,
serviceTwoData : serviceTwoData ,
getService1: function(){
var deferred = $q.defer();
$http({method: 'GET',url:'Service1',
headers:{
'Access-Control-Allow-Origin': '*'}}).
success(function(data,status,header,config){
serviceOneData = data;
deferred.resolve();
})
.error(function (data,status,header,config) {
deferred.reject();
});
//call service 2
return deferred.promise;
},
getService2: function(){
var Seconddeferred = $q.defer();
$http({method: 'GET',url:'Service2',
headers:{
'Access-Control-Allow-Origin': '*'}}).
success(function(data,status,header,config){
serviceTwoData = data;
Seconddeferred.resolve();
})
.error(function (data,status,header,config) {
Seconddeferred.reject();
});
//call service 2
return Seconddeferred.promise;
},
getService : getService1().then(getService2()).
then(
return serviceOneData + serviceTwoData
);
}
} );
You can chaining promise:
angular.module('app',[])
.factory('Service1',function($q,$timeout){
return {
f: function(){
var deferred = $q.defer();
$timeout(function() {
deferred.resolve('Service 1');
},1000);
return deferred.promise;
}
}
})
.factory('Service2',function($q,$timeout,Service1){
return {
f: function(){
var deferred = $q.defer();
Service1.f().then(function(data){
$timeout(function() {
deferred.resolve('Service 2 '+ data);
},1000);
});
return deferred.promise;
}
}
})
.controller('MainCtrl', function($scope,Service2){
var promise2 = Service2.f();
promise2.then(function(data2){
console.log(data2);
});
});
or just use
$q.all([Service1,Service2]).then
http://denisonluz.com/blog/index.php/2013/10/06/angularjs-returning-multiple-promises-at-once-with-q-all/
Your best bet is to use $q.all, which makes sure that all of the requested promises have been resolved before attempting to execute a function
function ServiceC(serviceA, serviceB, $q) {
var dataA = serviceA.GetAsyncStuff(); //returns promise
var dataB = serviceB.GetAsyncStuff(); //returns promise
$q.all([dataA, dataB]).then(doSomething);
}
EDIT: If you need to return the data, you're going to have to return a promise. You can either just return $q.all or resolve a new promise once $q.all has been resolved, e.g.
function ServiceC(serviceA, serviceB, $q) {
var dataA = serviceA.GetAsyncStuff(); //returns promise
var dataB = serviceB.GetAsyncStuff(); //returns promise
var deferred = $q.defer();
$q.all([dataA, dataB]).then(function (promises) {
deferred.resolve('Greetings ' + promises[0] + promises[1]);
});
return deferred.promise;
}