$http AngularJS calls stored in array sequentially - angularjs

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.

Related

Is it normal than my $http request get resolved in inverse order? (angularjs)

I have a function inside a service that makes $http.get request from an array
var ids = [1,2,3]
var extensionPromises = [];
for (var i = 0 ; i < extensions.length ; i++) {
var myPromise = myHttpService.getOneExtension(ids[i]);
extensionPromises.push(myPromise);
}
return $q.when(
Promise.all(extensionPromises).then(function(all){
$rootScope.$broadcast("allReady");
return true;
}).catch(function(error){
var e = {};
e.error = error;
e.error.type = "http.get";
return (e);
})
);
That send the ID to a simple $http.get( myUrl + id).then()..., all work ok but when I see the XHR get info in the console, it resolves in the inverse order, that is 3, 2, 1.
Is this normal?
There are no guarantees on order of resolve of multiple async requests. They are subject to network time, server time etc. Even 2 requests to same endpoint may not return in same order they were sent

chaining http post and q service in angular in serial fashion

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

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

Angularjs for loop issue

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.

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