Unable to loop through API response / access loop variables - javascript

I'm trying to build a simple Twitter feed app, but I'm having difficulty implementing a refresh function.
$scope.refreshTimeline = function() {
for (x = 0; x < $scope.tweetsarray.length; x++){ // loop through the twitter feeds
twitterService.getLatestTweets($scope.tweetsarray[x].name).then(function(data) {
$scope.tweetsarray[x].tweets = data; //update each
});
}
}
and the getlatesttweets function
getLatestTweets: function (name) {
//create a deferred object using Angular's $q service
var deferred = $q.defer();
var promise = authorizationResult.get('https://api.twitter.com/1.1/search/tweets.json?q='+name+'&count=8').done(function(data) {
//when the data is retrieved resolved the deferred object
deferred.resolve(data)
});
//return the promise of the deferred object
return deferred.promise;
}
So here are the issues I'm having
1) The value of x (global variable, unused anywhere else) in the .then(function(data) seems to be different from anywhere else in the loop and it doesn't seem to increment. I've tried using $scope.x or making it a function variable in the loop. The refresh works if I hardcode it to only refresh [0] in the tweetsarray, but[x] returns a separate value.
2) I'm guessing there is some kind of issue with trying to loop through a function with deferred promises. Is there a way I can make sure on each pass through the loop that the promise is returned before continuing?
Thanks I appreciate your help!
-Dave

Due to the closure behavior of javascript, it will take the latest x value at the time of callback from service. So try in this way.
$scope.refreshTimeline = function() {
for (var x = 0; x < $scope.tweetsarray.length; x++){
storeTweets(x);
}
}
function storeTweets(x){
twitterService.getLatestTweets($scope.tweetsarray[x].name).then(function(data) {
$scope.tweetsarray[x].tweets = data; //update each
});
}

You could do something like this
$scope.refreshTimeline = function() {
for (x = 0; x < $scope.tweetsarray.length; x++){ // loop through the twitter feeds
var data;
data.tweet = $scope.tweetsarray[x];
twitterService.getLatestTweets($scope.tweetsarray[x].name).then(function(data) {
$scope.tweetsarray[$scope.tweetsarray.indexof(data.tweet)].tweets = data; //update each
});
}
}
it means, pass the array element to the async task and it comes back in the response and then use it to find the array index and update the element

Related

Remove from datatable after callback is finished

I have a datatable, a checkbox on each table, and a button that will trigger my operation on that row. I would like to remove that row when my operation is done.
for (i = 0; i < checkedBoxes.length; i++) {
var chk = checkedBoxes[i];
var tdef = chk.closest("tr").querySelectorAll('td');
var myThing = tdef[1].innerHTML;
service.doSomething(myThing, function (result) {
service.doSomethingElse();
// I would like to remove this row once I'm done with this row
//browseDataTable.row($(chk).parents('tr')).remove().draw();
});
}
I know that I'm not supposed to remove that row as I'm looping through it. So I'm planning to just collect the index of each row, and when everything is finished, I can remove it, like this:
var myArr = new Array();
for (i = 0; i < checkedBoxes.length; i++) {
service.doSomething(myThing, function (result) {
service.doSomethingElse();
myArr.push(i);
}) // Chrome said 'then' is undefined, so how do I chain callback here?
.then(function () {
// Remove all rows at index in myArr
});
}
The service isn't async service, it's an ASMX service.
You are using your service both like a function with a callback and a Promise. So which is it? Does it take a callback, or does it return a Promise?
It looks like it does not return a Promise, because you are trying to chain .then() and it's undefined.
The service isn't async then why are you giving it a callback and trying to chain a .then(), if it's synchronous?
Anyway, one easy way to solve your issue is to use let, which will create a scope for every loop.
Currently :
for (i = 0; i < checkedBoxes.length; i++) { // i is a global (window) variable, that's bad
service.doSomething(myThing, function (result) {
service.doSomethingElse();
myArr.push(i); // i will always be checkboxes.length
})
}
By using let :
for (let i = 0; i < checkedBoxes.length; i++) { // i is in the local scope
service.doSomething(myThing, function (result) {
service.doSomethingElse();
myArr.push(i); // the value of i will be different (correct) each time
})
}

Count number of elements in $resource.query() object

I'm trying to count the number of elements returned from $resouce.query() object, so that I can increment it and use it as the ID for the next object to be saved.
Following is a service to communicate with the server:
eventsApp.factory('EventData', function($resource) {
var resource = $resource('/data/event/:id', {id: '#id'});
return {
getEvent: function(eventId) {
return resource.get({id: eventId});
},
saveEvent: function(event) {
var count = 0;
resource.query(function(data) {
count = data.length; // Accessible here!
});
event.id = count; // Not accessible here!
return resource.save(event);
},
getAllEvents: function() {
var count = 0;
var lol = resource.query(function(data) {
count = data.length;
});
console.log(count);
return resource.query();
}
}
});
However, as mentioned in the comments, I'm unable to access the length property. Any solutions?
By looking at your code what I am getting is your resource.query executes the callback function asynchronously because of that your event.id = count; is executed first and then the callback it executed. If you want to access data.length then you can use $q and create a defer. And then resolve that deferred object.

Scoping within Angular service call

I'm confused as to why I cannot get this service call to perform as needed. The console.log's within definitionsService.get promise resolution are what I would expect (the object I'm aiming to return). However the console.log right before I return defs is undefined which, of course, means my returned value is undefined. What am I missing?
function getDefinitions() {
var defs;
definitionsService.get().$promise.then(function(data) {
console.log(data);
defs = data;
console.log(defs);
});
console.log(defs);
return defs;
};
I changed the above to:
function getDefinitions() {
var defs = $q.defer();
definitionsService.get().$promise.then(function(data) {
defs.resovle(data);
});
return defs.promise;
};
per the below answer.
I also changed the way I call this method per the same answer like this:
function detail(account) {
getDefinitions().then(function(definitions) {
var key = angular.isDefined(definitions.ABC[account.code]) ? account.code : '-';
return definitions.ABC[key].detail;
});
}
Then in my controller I'm trying to do the following:
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
accounts[i].detail = utilitiesService.detail(accounts[i]);
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
My problem is that accounts[i].detail is consistently undefined.
Welcome to the world of asynchronous calls.
If you are making an async call inside getDefinitions (from definitonsService), then you must assume getDefinitions() is async as well. Which means you cannot simply return defs.
When you print defs before returning it, the async call of the service has yet to have been carried out.
defs should also be a promise object. You can then return it as you do, but the method invoking it should also use it with .then(function(defs)...
Or in code form:
function getDefinitions() {
var defs = $q.defer();
definitionsService.get().$promise.then(function(data) {
defs.resovle(data);
});
return defs.promise;
};
And whoever calls getDefinitions() :
getDefinitions().then(function(defs) {
// do something with defs...
}
Answer to question after edit:
The problem is once again with the async nature inside the getAccounts method.
utilitiesService.detail is an async method. So you are in fact assigning promises, not values, to accounts[i].detail.
Since each account will entail an async call, using $q.all seems like a good idea:
var promises = [];
for (var i = 0; i < accounts.length; i++) {
promises.push(utilitiesService.detail(accounts[i]));
}
$q.all(promises).then(function(values)){
// you should have all the account details inside 'values' and can update vm.accounts
}
remember - getting the promise is synchronous. Getting the value that the promise... well, promises to get you - that's asynchronous.
The problem is that you are returning defs before the promise has resolved, while defs === None.
You can solve this by changing your code a bit:
function getDefinitions() {
return definitionsService.get().$promise.then(function(data) {
return data;
});
};

Javascript closure access with callbacks inside loop

what shall I do to make the last row of code return a value?
$scope.runActionwithObjects = function() {
for (var i = 0; i < $scope.Objects.length; i++) {
console.log($scope.Objects[i]); //$scope is accessible
$http.get($scope.Objects[i]["Commit"]).success(function (data) {
console.log($scope.Objects[i]);//return undefined
The problem is due to asynchrony of ajax requests.
When the success callback is executed, your loop has already finished and the i variable is already equal to $scope.Objects.length.
Try forEach. This function will create different closures for items in the array.
$scope.Objects.forEach(function(currentObject){
console.log(currentObject); //$scope is accessible
$http.get(currentObject["Commit"]).success(function (data) {
console.log(currentObject);
});
});
The reason your $scope.Objects[i] is undefined because the variable i is always = $scope.Objects.lenth + 1, for example you got 5 elements, the i will be 6, because the at the time of callback, it already got the last value.
One solution is to bind needed object to that method, so we can access it via this(we can not reference directly by closure to ref variable, because it's still stored the last item), for example:
for (var i = 0; i < $scope.Objects.length; i++) {
var ref = $scope.Objects[i];
// console.log($scope.Objects[i]); //$scope is accessible
var successCallback = (function (data) {
console.log(this);//return the ref
}).bind(ref);
$http.get('').success(successCallback);
}
}

What does $.when.apply($, someArray) do?

I'm reading about Deferreds and Promises and keep coming across $.when.apply($, someArray). I'm a little unclear on what this does exactly, looking for an explanation that one line works exactly (not the entire code snippet). Here's some context:
var data = [1,2,3,4]; // the ids coming back from serviceA
var processItemsDeferred = [];
for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}
$.when.apply($, processItemsDeferred).then(everythingDone);
function processItem(data) {
var dfd = $.Deferred();
console.log('called processItem');
//in the real world, this would probably make an AJAX call.
setTimeout(function() { dfd.resolve() }, 2000);
return dfd.promise();
}
function everythingDone(){
console.log('processed all items');
}
.apply is used to call a function with an array of arguments. It takes each element in the array, and uses each as a parameter to the function. .apply can also change the context (this) inside a function.
So, let's take $.when. It's used to say "when all these promises are resolved... do something". It takes an infinite (variable) number of parameters.
In your case, you have an array of promises; you don't know how many parameters you're passing to $.when. Passing the array itself to $.when wouldn't work, because it expects its parameters to be promises, not an array.
That's where .apply comes in. It takes the array, and calls $.when with each element as a parameter (and makes sure the this is set to jQuery/$), so then it all works :-)
$.when takes any number of parameters and resolves when all of these have resolved.
anyFunction.apply(thisValue, arrayParameters) calls the function anyFunction setting its context (thisValue will be the this within that function call) and passes all the objects in arrayParameters as individual parameters.
For example:
$.when.apply($, [def1, def2])
Is the same as:
$.when(def1, def2)
But the apply way of calling allows you to pass an array of unknown number of parameters. (In your code, you are saying that you data comes from a service, then that is the only way to call $.when)
Here, the code fully documented.
// 1. Declare an array of 4 elements
var data = [1,2,3,4]; // the ids coming back from serviceA
// 2. Declare an array of Deferred objects
var processItemsDeferred = [];
// 3. For each element of data, create a Deferred push push it to the array
for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}
// 4. WHEN ALL Deferred objects in the array are resolved THEN call the function
// Note : same as $.when(processItemsDeferred[0], processItemsDeferred[1], ...).then(everythingDone);
$.when.apply($, processItemsDeferred).then(everythingDone);
// 3.1. Function called by the loop to create a Deferred object (data is numeric)
function processItem(data) {
// 3.1.1. Create the Deferred object and output some debug
var dfd = $.Deferred();
console.log('called processItem');
// 3.1.2. After some timeout, resolve the current Deferred
//in the real world, this would probably make an AJAX call.
setTimeout(function() { dfd.resolve() }, 2000);
// 3.1.3. Return that Deferred (to be inserted into the array)
return dfd.promise();
}
// 4.1. Function called when all deferred are resolved
function everythingDone(){
// 4.1.1. Do some debug trace
console.log('processed all items');
}
Unfortunately I can not agree with you guys.
$.when.apply($, processItemsDeferred).always(everythingDone);
Will call everythingDone as soon as one deferred gets rejected, even if there are other deferreds that are pending.
Heres the full script (I recommend http://jsfiddle.net/):
var data = [1,2,3,4]; // the ids coming back from serviceA
var processItemsDeferred = [];
for(var i = 0; i < data.length; i++){
processItemsDeferred.push(processItem(data[i]));
}
processItemsDeferred.push($.Deferred().reject());
//processItemsDeferred.push($.Deferred().resolve());
$.when.apply($, processItemsDeferred).always(everythingDone);
function processItem(data) {
var dfd = $.Deferred();
console.log('called processItem');
//in the real world, this would probably make an AJAX call.
setTimeout(function() { dfd.resolve(); }, 2000);
return dfd.promise();
}
function everythingDone(){
alert('processed all items');
}
It this a bug? I would like to use this like the gentleman above described it.
Maybe someone can find this useful:
$.when.apply($, processItemsDeferred).then(everythingDone).fail(noGood);
everythingDone isn't called in case of any reject
$.when alone makes it possible for a callback to be called when every promises passed to it are resolved/rejected. Normally, $.when takes a variable number of arguments, using .apply makes it possible to pass it an array of arguments, it's very powerful. For more info on .apply: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
Thanks for your elegant solution:
var promise;
for(var i = 0; i < data.length; i++){
promise = $.when(promise, processItem(data[i]));
}
promise.then(everythingDone);
Just one point: When using resolveWith to get some parameters, it breaks because of the initial promise set to undefined. What i did to make it work:
// Start with an empty resolved promise - undefined does the same thing!
var promise;
for(var i = 0; i < data.length; i++){
if(i==0) promise = processItem(data[i]);
else promise = $.when(promise, processItem(data[i]));
}
promise.then(everythingDone);

Categories