Angular JS waiting for all the promises to be resolved - angularjs

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
});

Related

$http request wrapped in Service returns the whole response in resolve state Provider

I have a state defined like this:
.state('list', {
url: '/list',
controller: 'ctrl',
resolve: {
data: ['DataService', function(DataService) {
return DataService.getList();
}]
}
})
The getList of DataService makes the http request:
var httpRequest = $http(categoryRequest);
httpRequest.then(function (response) {
return response.data;
})
.catch(function (error) {
console.log('could not get categories from server');
});
return httpRequest;
In controller I just assign the list to its list property:
function ctrl(data) {
this.list = data.data;
}
The problem:
No matter what I return in success callback of http request, I always get the whole response in resolve of state provider.
So I have to do data.data in controller to get the data from response.
Questions:
Is my assumption true that I will always get the whole reponse in resolve?
How to get just the data form response that I do not have to get it in controller.
Best regards,
var httpRequest = $http(categoryRequest);
So httpRequest is a Promise<Response>
httpRequest.then(function (response) {
This creates another promise, but this new promise is not assigned to anything.
return httpRequest;
This returns the original Promise<Response>.
You want
httpRequest = httpRequest.then(function (response) {
Or simply
return httpRequest.then(function (response) {
So that what you return is the new promise.
To give you a simpler analog example, your code is similar to
var a = 1;
a + 1;
return a;
That returns 1, not 2.
To return 2, you need
var a = 1;
a = a + 1;
return a;
or
var a = 1;
return a + 1;
There are two problems. First, the service is returning the original httpPromise and not the promise derived from the original promise. Second, the error handler is converting the rejection to a success.
var httpRequest = $http(categoryRequest);
//httpRequest.then(function (response) {
var derivedPromise = httpRequest.then(function onSuccess(response) {
//return to chain data
return response.data;
}).catch(function onReject(error) {
console.log('could not get categories from server');
//IMPORTANT to avoid conversion
throw error;
//OR
//return $q.reject(error);
});
//return httpRequest;
return derivedPromise;
The .then method of a promise returns a new promise derived from the original promise. It does not mutate the original promise.
A common problem is the omission of a throw or return $q.reject statement from a rejection handler. Functions without such statements return a value of undefined which will convert a rejection to a success which resolves as undefined.

AngularJS Service with multiple, depending queries

I'm trying to create an AngularJS service, which returns data based on several HTTP requests. But i seem to just not get it to work.
The REST call works as follow:
get /index which returns an array of urls
call each of the url's, and add the result to an array
I expect that at the end of the call of the service function, that i receive a data structure containing all the data from the url's.
My current, somewhat working code uses callbacks, but even though it works in one controller, it does not in another. I want to correctly use promises, but i'm already confused with success vs then.
My service:
// Get a image
obj.getByUrl = function (imageUrl, callback) {
$http.get('https://localhost:9000' + imageUrl).success(function (data) {
callback(data);
});
}
// Get all images
obj.getAll = function(callback) {
$http.get('https://localhost:9000/1.0/images').success(function (data) {
if (data.status != "Success") {
console.log("Err");
}
var images = [];
for(var n=0; n < data.metadata.length; n++) {
var c = data.metadata[n];
obj.getByUrl(c, function(data2) {
images.push(data2.metadata);
});
}
callback(images);
});
}
i'd like to use the service in a controller resolve like this:
resolve : {
images: function(ImagesServices, $route) {
return ImagesServices.getState($route.current.params.containerName)
},
I came as far as this, but it does only return the data of the index call, not the aggregated data:
obj.getAll3 = function() {
var images = [];
var promises = [];
//var httpPromise = $http.get('https://localhost:9000/1.0/images');
var httpPromise = $http({
url: 'https://localhost:9000/1.0/images',
method: 'GET',
});
return httpPromise.success(function(data) {
var data2 = data.metadata[0];
// angular.forEach(data.metadata, function(data2) {
console.log("D11: " + JSON.stringify(data2));
//var inPromise = $http.get('https://localhost:9000' + data2)
var inPromise = $http({
url: 'https://localhost:9000' + data2,
method: 'GET',
})
.success(function (data2) {
console.log("D2: " + JSON.stringify(data2));
images.push(data2);
});
promises.push(inPromise);
// });
return $q.all(promises).then(function() {
return images;
});
});
}
Maybe someone can point me into the right direction?
This is the typical case where chaining promises, and using $q.all(), is adequate:
/**
* returns a promise of array of images
*/
obj.getAll = function() {
// start by executing the first request
return $http.get('https://localhost:9000/1.0/images').then(function(response) {
// transform the response into a promise of images
// if that's not possible, return a rejected promise
if (data.status != "Success") {
return $q.reject("Error");
}
// otherwise, transform the metadata array into
// an array of promises of image
var promises = data.metadata.map(function(imageUrl) {
return $http.get('https://localhost:9000' + imageUrl).then(function(resp) {
return resp.data;
});
});
// and transform this array of promises into a promise
// of array of images
return $q.all(promises);
});
}
This avoid the callback antipattern, and uses chaining. It's a bit long to explain here, but I wrote a blog post that should, hopefully, make the above code clear: http://blog.ninja-squad.com/2015/05/28/angularjs-promises/

synchronous http call in angularJS

I have the following scenario, I need data from a particular url. I have written a function which takes parameter 'url'. Inside the function I have the $http.get method which makes a call to the url. The data is to be returned to the calling function
var getData = function (url) {
var data = "";
$http.get(url)
.success( function(response, status, headers, config) {
data = response;
})
.error(function(errResp) {
console.log("error fetching url");
});
return data;
}
The problem is as follows, $http.get is asynchronous, before the response is fetched, the function returns. Therefore the calling function gets the data as empty string. How do I force the function not to return until the data has been fetched from the url?
Take a look at promises to overcome such issues, because they are used all over the place, in angular world.
You need to use $q
var getData = function (url) {
var data = "";
var deferred = $q.defer();
$http.get(url)
.success( function(response, status, headers, config) {
deferred.resolve(response);
})
.error(function(errResp) {
deferred.reject({ message: "Really bad" });
});
return deferred.promise;
}
Here's a nice article on promises and $q
UPDATE:
FYI, $http service itself returns a promise, so $q is not necessarily required in this scenario(and hence an anti-pattern).
But do not let this be the reason to skip reading about $q and promises.
So the above code is equivalent to following:
var getData = function (url) {
var data = "";
return $http.get(url);
}
You can use $q.all() method also to solve this problem
var requestPromise = [];
var getData = function (url) {
var data = "";
var httpPromise = $http.get(url)
.success( function(response, status, headers, config) {
data = response;
})
.error(function(errResp) {
console.log("error fetching url");
});
requestPromise.push(httpPromise);
}
in the calling function
$q.all(requestPromise).then(function(data) {
//this is entered only after http.get is successful
});
make sure to inject $q as a dependency. Hope it helps
You function seems redundant. Just use $http.get(url), since you aren't really doing anything else before you use it anyway.
var url = 'foo/bar';
$http
.get(url)
.success( function(response, status, headers, config) {
$scope.data = response;
})
.error(function(errResp) {
console.log("error fetching url");
});
Or if you need to access the promise later just assign it to variable;
var promise = $http.get(url);
// some other code..
promise.then(function(data){
//.. do something with data
});
A typical way to do what you want is like so:
var getData = function(url, callback) {
$http.get(url).success(function(response) {
callback && callback(response);
});
};
Used like:
getData('/endpoint', function(data) {
console.log(data);
});

AngularJS $http success callback data return

Hello everyone :) Here is the problem. I'm making an angular app with:
a factory to access to an api with $http that retrieves an array of objects from a server
getObjectsFromApi : function(){
return $http({
url: 'http://path/to/the/api/',
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
}
})
},
and a service to compute retrieved data and inject it to controllers
this.getObjectsFromService = function(){
var objects = [];
ObjectFactory.getObject()
.success(function(data, status){
console.log("Successfully got objects !");
objects = data;
})
.error(function(data, status){
console.log("Fail... :-(");
});
return objects;
};
The problem is that when I return objects, it doesn't return any data. How can I do to return $http callback data within this getObjectsFromService function ?
Thank you for helping !
You must use promises, something like this should do the trick
this.getObjectsFromService = function(){
var defer = $q.defer();
ObjectFactory.getObject()
.then(function(data){
console.log("Successfully got objects !");
defer.resolve(data);
})
.catch(function(data){
console.log("Fail... :-(");
defer.reject(data);
});
return defer.promise;
};
And now you can use this function somewhere else like this:
var foo = function() {
var objects = [];
this.getObjectsFromService().then(function(data) {
objects = data;
//do rest of your manipulation of objects array here
});
}
There is no other way to return the objects, it's not possible because $http is asynchronous. You'll have to rework the rest of the code and adapt it to this
var req = ObjectFactory.getObject();
req.success(function(data) {...}
req.error(function(data) {...}
This will do it for ya
The variable req will then be the promise sent back from the factory.
Edit
Keep in mind that your data will not be changes until the promise is resolved, so if you try to console.log(objects) before then, it will be empty.
The http request is async, which means that it completes after you return objects. Return the promise instead:
this.getObjectsFromService = function(){
return ObjectFactory.getObject().catch(function(){
console.log("Fail... :-(");
});
}
Then
service.getObjectsFromService().then(function(resp) {
console.log("Successfully got objects !", resp.data);
});
Your code is asynchronous. When you return objects, you return your initial empty array. You can't return directly your object, but instead you need to return a promise (see 3 possibilities below)
By creating a promise
var deferred = $q.defer();
ObjectFactory.getObjectsFromApi()
.success(function(data, status){
console.log("Successfully got objects !");
// Do stuff and resolve promise
deferred.resolve(data);
})
.error(function(data, status){
console.log("Fail... :-("));
// Do stuff and reject promise
deferred.reject(data)
});
return deferred.promise;
You can use promise chaining (use .then rather than .success and .error):
Note: when using then with success and error callbacks rather than success and error methods, you have only 1 argument which is the response object
return ObjectFactory.getObjectsFromApi()
.then(function(response){
console.log("Successfully got objects !");
// Do stuff and chain full response headers or data
return responseHeaders;
// or
// return responseHeaders.data;
}, function(responseHeaders){
console.log("Fail... :-("));
// Do stuff and chain error (full response headers or data)
return $q.reject(responseHeaders)
// return $q.reject(responseHeaders.data);
});
Or if you have no business logic, or no reason to intervene in your factory, simply return your $http call directly:
return ObjectFactory.getObjectsFromApi();
Angular $resource
Factory
getObjectsFromApi : function(){
return $resource('http://path/to/the/api/', {},
{
get: {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
}
})
},
Service
this.getObjectsFromService = function(){
var objects = [];
ObjectFactory.get().$promise.then(function(data) {
objects = data;
return objects;
}).error(function(err) {
throw err;
});
};

Angularjs $q.all

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);
}

Resources