Angularjs for loop issue - angularjs

There's a for loop and inside the for loop I'm calling an AJAX request. The issue I encountered is, the for loop finishes before the requests complete.
I want the for loop to continue to it's next iteration only after the required AJAX request completes.
PS- AJAX works fine. I do get my desired information from the server. It's just the for loop iterations complete first without waiting for the AJAX request success function to fire up. So when the AJAX success function finally fires the value in the variable cid is inconclusive as it has been overwritten by the last iteration of the for loop.
I want the for loop to continue only after the AJAX success function is executed.
Code:
if (window.cordova) {
db = $cordovaSQLite.openDB("my.db"); //device
} else {
db = window.openDatabase("my.db", '1', 'my', 1024 * 1024 * 100); // browser
}
var query = "SELECT * FROM order_product";
$cordovaSQLite.execute(db, query, []).then(function(res) {
if (res.rows.length > 0) {
for (var i = 0; i < res.rows.length; i++) {
console.log(" foreach SELECTED shopcart-> " + res.rows.item(i).id);
var cid = res.rows.item(i).coffee_id;
$http.post("http://192.168.1.4/coffeepayWeb/public/index.php/getsugar", {
cid: cID
})
.success(function(result) {
console.log("success");
if (cid == 6) {
//do something
} else {
//do something else
}
});
}
}
}

Using for is unsafe for asynchronous operations if you need the iteration index, use it only to store the required values to make the async operation ($http.post in this case), it should looks like:
var items = [];
for (var i = 0; i < res.rows.length; i++) {
var item = res.rows.item(i);
items.push(item);
}
after consider that $http returns a promise, then you should be able to map all elements from items
var getsugarUrl = 'http://192.168.1.4/coffeepayWeb/public/index.php/getsugar';
// Map the values to obtain the promises on each $http operation, the allSettled method of the
// [Kriskowal Q library](https://github.com/kriskowal/q/wiki/API-Reference) will be simulated
// this is because when one of the http requests fail then the $q.all method break and reject with an error
var promises = items.map(function (item) {
var cid = item.coffee_id;
return $http.post(getsugarUrl, { cid: cid })
.then(function (result) {
// You can take advantage of this closure to handle the result of the request an the
// cid property, store your modified result on the value property from the return
return {
state: 'fullfilled',
value: {
result: result,
cid: cid
} // your modified result
};
})
// Handle when the http request fails
.catch(function (err) {
return {
state: 'rejected',
error: err
};
});
});
finally handle the results obtained using $q.all (you need to inject the $q service)
$q.all(promises)
.then(function (responses) {
// iterate over the results
responses
.filter(function(response) { // only fullfilled results
return response.state == 'fullfilled';
})
.forEach(function (response) {
if (response.value.cid == 6) {
//do something with response.value.result
} else {
//do something else
}
});
});
With this solution the http requests aren't resolved sequentially, but you have control over when they've finished together and you will have the correct value of cid
Check more about JavaScript Promises

$http uses promises, which means you need to think of the problem within the promise paradigm.
Consider a recursive option where you pass in an array of your cID's, and each call sends a $http.post for the 1st cID in the array; if the call succeeded, we continue recursively with a smaller array, until there are no more left.
One promise is created & returned in the 1st call, which is notified on each successful query (allowing you to do your per-cID logic), and finally resolved when all queries are done (or rejected if any query fails).
// This function is called without deferred;
// deferred is used on recursive calls inside the function
function doPost(url, cidList, deferred) {
if (deferred === undefined) {
deferred = $q.defer();
}
var cid = cidList[0];
$http.post(url, {cid: cid})
.success(function(result) {
// query succeeded; notify the promise
deferred.notify({cid: cid, result: result});
if (cidList.length > 1) {
// there are more items to process; make a recursive
// call with cidList[1:end]
doPost(url, cidList.slice(1), deferred);
} else {
// we're done; resolve the promise
deferred.resolve();
}
})
.error(function(message) {
// there was an error; reject the promise
deferred.reject({cid: cid, message: message});
});
return deferred.promise;
}
// build the list of cIDs to pass into doPost
var cidList = [];
for (var i = 0; i < res.rows.length; i++) {
cidList.push(res.rows.item(i).coffee_id);
}
// start the queries
doPost("http://192.168.1.4/coffeepayWeb/public/index.php/getsugar", cidList)
.then(function() {
// promise resolved
console.log("All done!");
}, function(info) {
// promise rejected
console.log("Failed on cID " + info.cid + ": " + info.message);
}, function(info) {
// promise being notified
console.log("Just did cID " + info.cid + ": " + info.result);
// your per-cid handler
if (info.cid == 6) {
// do something
} else {
// do something else
}
});
UPDATE
Since the motivation for the question had more to do with variable scope (rather than sequential HTTP requests), this is all you really need:
// Build the CID list so that we can iterate it
var cidList = [];
for (var i = 0; i < res.rows.length; i++) {
cidList.push(res.rows.item(i).coffee_id);
}
// Iterate the list & call $http.post
cidList.forEach(function(cid) {
// your $http.post() logic; each call will have its own
// cid thanks to closures
});
Each iteration will have it's own cid, which you can use in your .success() or .error() handlers without worrying about it being overwritten. As with the other solution, the requests aren't sequential, but you probably didn't need them to be in the first place.

Related

dealing with an array of objects with promises

I am trying to make a node express app where I fetch data from different url's making a call to node-fetch to pull the body of some pages and other information about certain url endpoints. I want to then render a html table to display this data through an array of information. I am having trouble with the call to render the information as all the functions are asynchronous making it difficult to make sure all the promise calls have been resolved before making my call to render the page. I have been looking into using bluebird and other promise calls of .finally() and .all() but they don't seem to work on my data as it is not an array of promise calls, but an array of objects. Each object was 4 promise calls to fetch data relating to a column of my table all in one row. Is there a function or specific way to render the page after all promises are resolved?
var express = require('express');
var fetch = require('node-fetch');
fetch.Promise = require('bluebird');
var router = express.Router();
const client = require('../platform-support-tools');
function makeArray() {
var registry = client.getDirectory();
var data_arr = [];
for (var i = 0; i < registry.length; i++) {
var firstUp = 0;
for (var j = 0; i < registry[i]; j++) {
if (registry[i][j]['status'] == 'UP') {
firstUp = j;
break;
}
}
var object = registry[i][firstUp];
data_arr.push({
'name': object['app'],
'status': object['status'],
'swagUrl': object['homePageUrl'] + 'swagger-ui.html',
'swag': getSwag(object),
'version': getVersion(object['statusPageUrl']),
'timestamp': getTimestamp(object['statusPageUrl']),
'description': getDescription(object['healthCheckUrl'])
});
}
return data_arr;
}
function getSwag(object_in) {
var homeUrl = object_in['homePageUrl'];
if (homeUrl[homeUrl.length - 1] != '/'){
homeUrl += '/';
}
var datum = fetch(homeUrl + 'swagger-ui.html')
.then(function (res) {
return res.ok;
}).catch(function (err) {
return 'none';
});
return datum;
}
function getVersion(url_in) {
var version = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['version'];
}).catch(function (error) {
return 'none';
});
return version;
}
function getTimestamp(url_in) {
var timestamp = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['timestamp'];
}).then(function (res) {
return body['version'];
}).catch(function (error) {
return 'none';
});
return timestamp;
}
function getDescription(url_in) {
var des = fetch(url_in)
.then(function(res) {
return res.json();
}).then(function(body) {
return body['description'];
}).catch(function (error) {
return 'none';
});
return des;
}
/* GET home page. */
router.get('/', function (req, res, next) {
var data_arr = makeArray();
Promise.all(data_arr)
.then(function (response) {
//sorting by app name alphabetically
response.sort(function (a, b) {
return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0);
});
res.render('registry', {title: 'Service Registry', arr: response})
}).catch(function (err) {
console.log('There was an error loading the page: '+err);
});
});
To wait on all those promises, you will have to put them into an array so you can use Promise.all() on them. You can do that like this:
let promises = [];
for (item of data_arr) {
promises.push(item.swag);
promises.push(item.version);
promises.push(item.timestamp);
promises.push(item.description);
}
Promise.all(promises).then(function(results) {
// all promises done here
})
If you want the values from all those promises, back into the object that's a bit more work.
let promises = [];
for (item of data_arr) {
promises.push(item.swag);
promises.push(item.version);
promises.push(item.timestamp);
promises.push(item.description);
}
Promise.all(promises).then(function(results) {
// replace promises with their resolved values
let index = 0;
for (let i = 0; i < results.length; i += 4) {
data_arr[index].swag = results[i];
data_arr[index].version = results[i + 1];
data_arr[index].timestamp = results[i + 2];
data_arr[index].description = results[i + 3];
++index;
});
return data_arr;
}).then(function(data_arr) {
// process results here in the array of objects
});
If you had to do this more often that just this once, you could remove the hard coding of property names and could iterate all the properties, collect the property names that contain promises and automatically process just those.
And, here's a more general version that takes an array of objects where some properties on the objects are promises. This implementation modifies the promise properties on the objects in place (it does not copy the array of the objects).
function promiseAllProps(arrayOfObjects) {
let datum = [];
let promises = [];
arrayOfObjects.forEach(function(obj, index) {
Object.keys(obj).forEach(function(prop) {
let val = obj[prop];
// if it smells like a promise, lets track it
if (val && val.then) {
promises.push(val);
// and keep track of where it came from
datum.push({obj: obj, prop: prop});
}
});
});
return Promise.all(promises).then(function(results) {
// now put all the results back in original arrayOfObjects in place of the promises
// so now instead of promises, the actaul values are there
results.forEach(function(val, index) {
// get the info for this index
let info = datum[index];
// use that info to know which object and which property this value belongs to
info.obj[info.prop] = val;
});
// make resolved value be our original (now modified) array of objects
return arrayOfObjects;
});
}
You would use this like this:
// data_arr is array of objects where some properties are promises
promiseAllProps(data_arr).then(function(r) {
// r is a modified data_arr where all promises in the
// array of objects were replaced with their resolved values
}).catch(function(err) {
// handle error
});
Using the Bluebird promise library, you can make use of both Promise.map() and Promise.props() and the above function would simply be this:
function promiseAllProps(arrayOfObjects) {
return Promise.map(arrayOfObjects, function(obj) {
return Promise.props(obj);
});
}
Promise.props() iterates an object to find all properties that have promises as values and uses Promise.all() to await all those promises and it returns a new object with all the original properties, but the promises replaced by the resolved values. Since we have an array of objects, we use Promise.map() to iterate and await the whole array of those.

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

AngularJS chaining promises - need to do work before the next 'then'

I am working on a promise chain. The first call is an $http call to check if a user exists, and then if it does, theres a bunch of .then() statements that run sequentially.
My question is this.. in that first call, i don't want to return the promise of the $http request because if the user doesn't exist, the results are just an empty array and the promise resolves, thus triggering the next action to look up information about the user. I wrote the following code...
(see the part in comments about being the important part i'm asking about)
$scope.checkIfUserExists = function() {
if (angular.isObject($scope.admin.Inductee.Contactor)) {
var handleFault = function( fault ) {
if (typeof(fault) === 'string') {
switch (fault.toUpperCase()){
case 'NODATA':
// Go ahead an save
$scope.pushInductee();
break;
case 'STATUS':
// just get the 'duplicate records check' sign off of there
// The save button is disabled by the critical error
$scope.hideSave = false;
break;
case 'ASSIGNED':
// just get the 'duplicate records check' sign off of there
// The save button is disabled by the critical error
$scope.hideSave = true;
break;
default:
$log.error(fault);
$location.path('/error/default');
}
} else {
$log.error(fault);
$location.path('/error/default');
}
};
$scope.getMatchingIndData()
.then($scope.procBusLogic)
.then($scope.pushInductee)
.catch(handleFault);
}
};
////HERE IS THE IMPORTANT PART I AM ASKING ABOUT
$scope.getMatchingIndData = function() {
var deferred = $q.defer();
var locals = {};
var checkUser = function(dupeJson){
var checkUserDeferred = $q.defer();
// abandoned promise replaced with my own
sttiJoinDataFactory.checkIfUserExistsNurseleader(dupeJson)
.then(function(results) {
var data = results.data;
if (angular.isArray(data) && data.length > 0){
var highestMatch = data[0];
for (var i = 0; i < data.length; i++) {
if (parseInt(data[i].Score) > parseInt(highestMatch.Score)) {
highestMatch = data[i];
}
}
checkUserDeferred.resolve(highestMatch);
} else {
// Reject the 'overall' promise here
// to effectively break the chain
return deferred.reject('NODATA');
}
})
.catch(function(fault) {
// Any other failure should break the chain
// of http requests at this point
return deferred.reject(fault);
});
return checkUserDeferred.promise;
},
loadindividual = function (highestMatch) {
return $http stuff about the highestmatch
// set data in locals
},
parallelLoadStatusAndInducteeData = function(individual) {
return another $http promise based on the last then()
// set data in locals
},
loadCeremonyData = function (inductees){
return another $http promise based on the last call then() // set data in locals
},
reportProblems = function( fault ) {
deferred.reject(fault);
};
checkUser($scope.generateDupJson())
.then(loadindividual, reportProblems)
.then(parallelLoadStatusAndInducteeData, reportProblems)
.then(loadCeremonyData, reportProblems)
.then(function() {
deferred.resolve(locals);
})
.catch( reportProblems );
return deferred.promise;
};
Must I take into account the abandoned promise, since I really need to promise to resolve when the data comes back, and i need to reject it if there is NODATA. This is handled in the calling function's chain.
Also, I'm aware of antipatterns here. I'm trying my best to not nest promises, maintain the chain, as well as handle exceptions.
Ok I have a few comments for you:
...
// revert if and return immediately
// to reduce indentation
if (typeof(fault) !== 'string') {
$log.error(fault);
$location.path('/error/default');
return;
}
switch (fault.toUpperCase()) {
...
You don't need deferred objects:
var checkUser = function(dupeJson){
// this is not abandoned because we are returning it
return sttiJoinDataFactory.checkIfUserExistsNurseleader(dupeJson)
.then(function(results) {
var data = results.data;
if (!angular.isArray(data) || data.length <= 0) {
return $q.reject('NODATA');
}
var highestMatch = data.reduce(function (highest, d) {
return parseInt(d.Score) > parseInt(highest.Score) ?
d : highest;
}, data[0]);
return highestMatch;
}); // you don't need catch here if you're gonna reject it again
}
...
checkUser(...)
// loadIndividual will be called
// after everything inside checkUser resolves
// so you will have your highestMatch
.then(loadIndividual)
.then(parallelLoadStatusAndInducteeData)
.then(loadCeremonyData)
// you don't need to repeat reportProblems, just catch in the end
// if anything rejects prior to this point
// reportProblems will be called
.catch(reportProblems)
...

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