chaining http post and q service in angular in serial fashion - angularjs

I have this code in angular,
$http({
method:'POST',
url :'someURL', //returns an array of urls [url1, url2, url3..]
data : dataObj
})
.then(function(response) {
var items = response.data;
var promises = [];
$scope.output =[];
items.forEach(function(el){
return promises.push($http.get(el)); //fills the promise[] array
});
var ignore = function(x) { return x.catch(function(){}); } // To ignore if promise does not get resolved (only accept responses with status 200)
var all = $q.all( promises.map(ignore) ); //chaining promises array
all.then(function success(d){
console.log($scope.output); //want the output to be ["text1", "text2", "text3"...]
});
for (var i=0; i < promises.length ; i++){
promises[i].then(success).catch(function (){
});
function success(r){
$scope.output.push(r.data.text); //{text: "text1"}
}
}
});
The result of this operation is stored in $scope.output. On executing I'm getting output as ["text2", "text3", "text1" ...] which is not in a serial fashion. My question is how I can make this execute in a serial fashion so that the output would be ["text1", "text2", "text3" ...]

Replace your last for loop with the following:
angular.forEach(promises, function(promise, index){
promise.then(success).catch(function (){});
function success(r){
$scope.output[index] = r.data.text;
}
});
Due to closure paradigm the index variable will be available in the success handler upon promise resolution no matter in which order the promises get resolved and the results will be placed to the output array in the order of the original promises.

Didn't test it, but from first view I'd say you need to put the for() loop inside the all.then().
all.then(function success(d){
console.log($scope.output);
for (var i=0; i < promises.length ; i++) {
promises[i].then(success).catch(function () { });
function success (r) {
$scope.output.push(r.data.text);
}
}
});
Because otherwise you loop through partially unresolved promises. Those that resolve earlier will skip ahead in the queue.
Having the for() loop inside the all.then() you make sure that all promises have resolved already and will add themselves to the output list when they are called with promises[i].then(success).

IMO,you should not use callbacks inside for loop. I think, it is causing this behavior. I hope it will work. No need to add last for loop.
var all = $q.all( promises.map(ignore) );
all.then(function success(d){
d.forEach(function(res){
$scope.output.push(r.data.text);
});
console.log($scope.output);
});

Related

AngularJS: promise in a loop

I am unable to do the promise looping.
I make a service call to get list of providers, then for each provider, I make another service call to get a customer.
A provider has 1 or more customers. So eventual list of customer is to be decorated and displayed.
In other format I am trying to achieve:
*serviceA.getProvider(){
foreach(providers){
foreach(provider.customerID){
serviceB.getCustomer(customerId)
}
}
}
.then(
foreach(Customer){
updateTheCustomer;
addUpdatedCustomerToAList
}
displayUpdatedCustomreList();
)*
I have written following code, that isn't working
doTheJob(model: Object) {
let A = [];
let B = [];
let fetchP = function(obj) {
obj.Service1.fetchAllP().then(function (response) {
let P = cloneDeep(response.data);
_.forEach(P, function(prov) {
_.forEach(prov.CIds, function(Id) {
A.push(Id);
});
});
_.forEach(A, function(CId) {
return obj.Service2.getById(CId);//what works is if this statement was: return obj.Service2.getById(A[0]);
//So, clearly, returning promise inside loop isn't working
});
})
.then(function(response) {
B.push(response.data); //This response is undefined
angular.forEach(B, function (value) {
obj.updateAdr(value)
});
obj.dispay(B);
});
};
fetchP(this);
}
forEach don't stop when you use return inside of it, try to use a plain loop instead, why you don't just loop with for ?
_.forEach(A, function(CId) {
return obj.Service2.getById(CId);
}
as stated by #Ze Rubeus if you return inside a callback within a for loop that value will be lost, since it's not returned to the caller.
probably you wanted something like this
return Promise.all(A.map(function(CId){
//collect each promise inside an array that will then be resolved
return obj.Service2.getById(CId);
})

$http AngularJS calls stored in array sequentially

I've been looking and looking everywhere for an example of how to get this to work appropriately. I've tried using $q.all() and it doesn't work. I can't seem to get promises to work appropriately or I'm not accessing them correctly. I'm making an API call to retrieve information about movies and I want to keep them ordered by release date. The easiest way would be to keep the call in the order I make them. I order the movie ids by release date and call them in that array order. Then I want to push the data from the call to a new array. But it's instead not always doing it in the correct order. Could someone possibly tell me what I may be doing wrong?
$scope.movies = [
{url:"tt3470600", group:"m",youtube:"Vso5o11LuGU", showtimes: "times1"},
{url:"tt3521164", group:"m",youtube:"iAmI1ExVqt4", showtimes: "times2"}
];
$scope.imdb = function () {
var promises = [];
for(var i = 0; i < $scope.movies.length; i++) {
var movie = $scope.movies[i];
var options = {trailer: movie.youtube, times: $scope.times[movie.showtimes]};
var promise = $http.get('http://www.omdbapi.com/?i=' + movie.url);
promise.times = options;
promises.push(promise);
};
return $q.all(promises);
};
var x = $scope.imdb();
console.log(x);
What's returned is an object d with a key of $$state. I would love to keep the order desperately because the times I return have a date selection that I would like to keep ordered.
I think you just missed something important here which is
var deferred = q.defer(); //init promise
and also
deferred.resolve(item); // resolve the promise
besides that
don't forget to handle error cases -> use deferred.reject(item) for those
Once you have done with all your promise, save all the results into the array
var arr = [];
q.allSettled(promises).then(function(results) {
arr = results;
});
You can use $q in a func to return a promise and make the http call inside that function and then call this based on the order you desire to get the array of promises.
var ajaxcallURl = {
0: 'https://api.github.com/users?since=84',
1: 'https://api.github.com/search/users?q=tyler',
2: 'https://api.github.com/users?since=357',
3: 'https://api.github.com/users?since=19990',
4: 'https://api.github.com/search/users?q=john',
5: 'https://api.github.com/users?since=2345',
6: 'https://api.github.com/users?since=1899',
7: 'https://api.github.com/search/users?q=james',
8: 'https://api.github.com/users?since=786',
9: 'https://api.github.com/search/users?q=nicholas',
10: 'https://api.github.com/users?since=99'
}
var SomeAsyncCall = function () {
var status_deferred = $q.defer();
var requestUrl = ajaxcallURl[count];
$http.get(requestUrl).
success(function (data, status, headers, config) {
status_deferred.resolve(data);
}).error(function (errdata, status, header, config) {
//requestData call failed, pass additional data with the reject call if needed
status_deferred.reject(errdata);
});
return status_deferred.promise;
}
With this promise array you can use $q.all to resolve all those and get the results when all those promises are done.
function createPromisesArray() {
var promiseArray = [];
for (var i=0;i<10;i++){
promiseArray.push(SomeAsyncCall());
}
return promiseArray;
}
var lstPromised = createPromisesArray();
$q.all(lstPromised).then((values) => {
console.log(values[0]);
console.log(values[1]);
// ....
console.log(values[9]);
values.forEach(function (result) {
console.log(result)
});
even though $q.all executes all promises asynchronously , you can get the appropriate promise result from the array.

chained promise in for loop doesn't execute properly

I have chainable promises which are working fine in a single series but now i want to call this serios of chain inside for loop but it does not work as expected.
see my demo plunker and see the output in console.
below is the structure of my chaining promises . I want all publicIP which is returned by funTwo; but I want to complete funThree() and then want to get all publicIP. As I know that $q.when() makes a value in promise object.
but you can see that console.log('pA', promiseArray); executed very before and console.log('res three'); and why successHandler and finally called before that?
Here surely I am missing something , may be have to write a return; in proper place , kindly help me how to executed all function in for loop and return a data array after that for loop ends which can be retried in successHandler
MyService.funZero()
.then(function(response) {
console.log(response);
var promiseArray = [];
for(var i = 0; i < 2 ; i++) {
console.log('I', i);
MyService.funOne()
.then(MyService.funTwo)
.then(function(res2) {
console.log('res two', res2);
publicIP = res2.ip;
console.log('i', publicIP);
promiseArray.push({'ip': publicIP});
return MyService.funThree(publicIP);
})
.then(function() {
console.log('res three');
})
} // for loop ends
console.log('pA', promiseArray);
return $q.when(promiseArray);
})
.then(function(res4){
console.log('after for loop', res4);
})
.then(successHandler)
.catch(errorHandler)
.finally(final, notify);
So, I'm not sure exactly what MyService.funThree does, but you can aggregate an array via ipArray.push({'ip': publicIP}) and return that to the MyService.funThree and then the subsequent function. The issue here is there is no guarantee of order in the ipArray if that's what you're looking for. Here's the middle section of that function:
ipArray = [];
for(var i = 0; i < 2 ; i++) {
console.log('I', i);
var promise = MyService.funOne()
.then(MyService.funTwo)
.then(function(res2) {
console.log('res two', res2);
publicIP = res2.ip;
console.log('i', publicIP);
ipArray.push({'ip': publicIP});
return ipArray;
})
.then(MyService.funThree)
.then(function() {
console.log('res three');
});
promiseArray.push(promise);
}
console.log('pA', promiseArray);
return $q.all(promiseArray);

Using _.each and $q promise to iterate widgets

I have a pretty straight-forward problem where I'm :
Iterating through a series of dashboard "widgets" using _.each().
Calling a function to refresh the current widget, and returning a $q promise.
Now, my issue is that I would like each iteration to WAIT prior to continuing to the next iteration.
My first version was this, until I realized that I need to wait for updateWidget() to complete:
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
updateWidget(wid, parentWidgetData);
}
});
My second version is this one, which returns a promise. But of course, I still have the problem where the iteration continues without waiting :
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
updateWidget(wid, parentWidgetData).then(function(data){
var i = 1;
});
}
});
and the called function which returns a deferred.promise object (then makes a service call for widget data) :
function updateWidget(widget, parWidData) {
var deferred = $q.defer();
// SAVE THIS WIDGET TO BE REFRESHED FOR THE then() SECTION BELOW
$rootScope.refreshingWidget = widget;
// .. SOME OTHER VAR INITIALIZATION HERE...
var url = gadgetDataService.prepareAggregationRequest(cubeVectors, aggrFunc, typeName, orderBy, numOrderBy, top, filterExpr, having, drillDown);
return gadgetDataService.sendAggGetRequest(url).then(function (data) {
var data = data.data[0];
var widget = {};
if ($rootScope.refreshingWidget) {
widget = $rootScope.refreshingWidget;
}
// BUILD KENDO CHART OPTIONS
var chartOptions = chartsOptionsService.buildKendoChartOptions(data, widget);
// create neOptions object, then use jquery extend()
var newOptions = {};
$.extend(newOptions, widget.dataModelOptions, chartOptions);
widget.dataModelOptions = newOptions;
deferred.resolve(data);
});
return deferred.promise;
}
I would appreciate your ideas on how to "pause" on each iteration, and continue once the called function has completed.
thank you,
Bob
******* UPDATED ************
My latest version of the iteration code include $q.all() as follows :
// CREATE ARRAY OF PROMISES !!
var promises = [];
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
promises.push(updateWidget(wid, parentWidgetData));
}
});
$q.all(promises)
.then(function () {
$timeout(function () {
// without a brief timeout, not all Kendo charts will properly refresh.
$rootScope.$broadcast('childWidgetsRefreshed');
}, 100);
});
By chaining promises
The easiest is the following:
var queue = $q.when();
_.each(widgets, function (wid) {
queue = queue.then(function() {
if (wid.dataModelOptions.linkedParentWidget) {
return updateWidget(wid, parentWidgetData);
}
});
});
queue.then(function() {
// all completed sequentially
});
Note: at the end, queue will resolve with the return value of the last iteration
If you write a lot of async functions like this, it might be useful to wrap it into a utility function:
function eachAsync(collection, cbAsync) {
var queue = $q.when();
_.each(collection, function(item, index) {
queue = queue.then(function() {
return cbAsync(item, index);
});
});
return queue;
}
// ...
eachAsync(widgets, function(wid) {
if (wid.dataModelOptions.linkedParentWidget) {
return updateWidget(wid, parentWidgetData);
}
}).then(function() {
// all widgets updated sequentially
// still resolved with the last iteration
});
These functions build a chain of promises in the "preprocessing" phase, so your callback is invoked sequentially. There are other ways to do it, some of them are more efficient and use less memory, but this solution is the simplest.
By delayed iteration
This method will hide the return value even of the last iteration, and will not build the full promise chain beforehands. The drawback is that, it can be only used on array like objects.
function eachAsync(array, cbAsync) {
var index = 0;
function next() {
var current = index++;
if (current < array.length) {
return $q.when(cbAsync(array[current], current), next);
}
// else return undefined
}
// This will delay the first iteration as well, and will transform
// thrown synchronous errors of the first iteration to rejection.
return $q.when(null, next);
}
This will iterate over any iterable:
function eachAsync(iterable, cbAsync) {
var iterator = iterable[Symbol.iterator]();
function next() {
var iteration = iterator.next();
if (!iteration.done) {
// we do not know the index!
return $q.when(cbAsync(iteration.value), next);
} else {
// the .value of the last iteration treated as final
// return value
return iteration.value;
}
}
// This will delay the first iteration as well, and will transform
// thrown synchronous errors of the first iteration to rejection.
return $q.when(null, next);
}
Keep in mind that these methods will behave differently when the collection changes during iteration. The promise chaining methods basically build a snapshot of the collection at the moment it starts iteration (the individual values are stored in the closures of the chained callback functions), while the latter does not.
Instead of trying to resolve each promise in your _.each(), I would build out an array of promises in your _.each to get an array like:
promises = [gadgetDataService.sendAggGetRequest(url1), gadgetDataService.sendAggGetRequest(url2)....]
Then resolve them all at once, iterate through the results and set your models:
$q.all(promises).then(function(results){ // iterate through results here })

How can I wait for $http response before continuing with angular.forEach loop

I'm making an AJAX request for each item in a loop, the end REST service can only perform one request at a time so I need the loop to wait for each request to complete before continuing with the next. How do I do this?
For reference, the end service is performing update tasks on DynamoDB tables - only one table can be modified at once hence my requirement to wait until I get a response before continuing. I could send them all to the server in one hit and handle there, although that makes it hard to receive feedback when each update is completed.
angular.forEach($scope.someArray,
function (value) {
var postdata = {
bla: value
};
$http.post('/some_url', postdata)
.then(
function(result) {
console.log("Success.");
},
function(data) {
console.log("Failure.");
}
);
}
);
Do you really need the forEach? I would not use it and go for something like that:
function req(arr) {
if (angular.isArray(arr) && arr.length > 0) {
var postdata = {
bla: arr[0]
};
$http.post('/some_url', postdata)
.then(
function(result) {
console.log("Success.");
arr.shift();
req(arr);
},
function(data) {
console.log("Failure.");
// if you want to continue even if it fails:
//arr.shift();
//req(arr);
}
);
}
}
req($scope.someArray);
If you really must make one request at a time (are you sure there isn't a more efficient alternative?), then you'll have to use something other than Angular.forEach.
What about something like this (you will need to inject $q):
function doWhateverWithAll(someArray) {
// Mark which request we're currently doing
var currentRequest = 0;
// Make this promise based.
var deferred = $q.deferred();
// Set up a result array
var results = []
function makeNextRequest() {
// Do whatever you need with the array item.
var postData = someArray[currentRequest].blah;
$http.post('some/url', postData)
.then( function (data){
// Save the result.
results.push(data);
// Increment progress.
currentRequest++;
// Continue if there are more items.
if (currentRequest < someArray.length){
makeNextRequest();
}
// Resolve the promise otherwise.
else {
deferred.resolve(results);
}
});
// TODO handle errors appropriately.
}
// return a promise for the completed requests
return deferred.promise;
}
Then, in your controller/service, you can do the following:
doWhateverWithAll($scope.someArray)
.then(function(results){
// deal with results.
});

Resources