I'm trying to iterate through an array of RSS feeds as below:
var rssFeeds = [ ['http://www.huffingtonpost.com/tag/womens-rights/feed/', "Huffington Post"], ['http://abcnews.go.com/topics/urss?pageid=681900', "ABC News"], ['http://www.globalissues.org/news/topic/166/feed', "Global Issues"], ['http://topics.nytimes.com/top/reference/timestopics/subjects/f/feminist_movement/index.html?rss=1', "The New York Times"] ];
This array contains both the location of the feed and a string with the name of the feed. I iterate through the array with a for loop as below, the one with i as an iterator.
Once I receive the results, I iterate through the results in the callback function with the for loop j. I append each fetched result to another array entryArray, and after appending each result, I add a new attribute 'source' to that fetched result using the name for the RSS feed.
function loadEntries()
{
var feedr = new Array();
for( var i = 0; i < rssFeeds.length; i++ )
{
feedr[i] = new google.feeds.Feed( rssFeeds[i][0] );
feedr[i].setNumEntries(loadAmount);
feedr[i].load(function(result) {
if (!result.error) {
console.log(i);
for (var j = 0; j < result.feed.entries.length; j++) {
entryArray[entryArray.length] = result.feed.entries[j];
entryArray[entryArray.length - 1]['source'] = rssFeeds[i][1];
}
}
});
}
}
However, and this is where the problem arises, the iterator I use (i) to indicate the name to append is always equal to rssFeeds.length, because the callback functions for all four load commands occur after the initial for loop has already finished iterating. The console.log(i); you see always returns 4.
This worked when I copied and pasted the code for each item individually, but I'd rather not copy and paste because the RSSFeeds array will probably be much longer in the future. Is there any way I can accomplish this with a loop?
Consider the following JSFiddle example
The reason your code does not function as intended is because the value of the variable i, will be i = 4 at the end of the loop and not 0,1,2,3 as desired because this would already have happened been incremented by the for loop.
The trick is to use a recursive function and your load function will function similarly to:
feedr.load(function (result) {
if (!result.error) {
console.log(i);
for (var j = 0; j < result.feed.entries.length; j++) {
entryArray[entryArray.length] = result.feed.entries[j];
entryArray[entryArray.length - 1]['source'] = rssFeeds[i][1];
}
if (rssFeeds.length - 1 > i) {
loadEntries();
i++;
}
}
});
So first of all, I'd like to thank nd_macias for providing me with the links that helped me find this solution. Basically, I wrapped the load function in a function, and then called that function with the for loop as below:
function loadEntries()
{
var feedr = new Array();
for( var i = 0; i < rssFeeds.length; i++ )
{
feedr[i] = new google.feeds.Feed( rssFeeds[i][0] );
feedr[i].setNumEntries(loadAmount);
var f = function(n) {
feedr[n].load(function(result) {
if (!result.error) {
for (var j = 0; j < result.feed.entries.length; j++) {
entryArray[entryArray.length] = result.feed.entries[j];
entryArray[entryArray.length - 1]['source'] = rssFeeds[n][1];
}
}
});
}
f(i);
}
}
Related
I ask this question again as user Cerbrus have marked the previous question as a duplicate of this question.
Can someone be so kind to show me how the question indicated by this user, should solve the code below? I can't find a match between those situations (even thought they are similar).
I need to pass a variable to a function inside a for loop. Here's an example:
var mainObj = [],
subArr = ['val1', 'val2'],
tmp;
for (var i = 0; i < subArr.length; i++) {
tmp = subArr[i];
mainObj.push({
key: function(varsFromLibrary) {
myFunc(tmp);
}
});
}
Here I have 2 problems:
why do i have to assign subArr[i] to tmp? Using myFunc(subArr[i]) will return that i is undefined?
why in myFunc i only receive the last value of subArr array?
UPDATE
I've updated the code as follows but i get TypeError: funcs[j] is not a function
var mainObj = [],
subArr = ['val1', 'val2'],
tmp,
funcs = [];
function createfunc(i) {
return function() { console.log("My value: " + i); };
}
for (var i = 0; i < subArr.length; i++) {
funcs[i] = createfunc(subArr[i]);
}
for (var j = 0; j < subArr.length; j++) {
tmp = subArr[i];
mainObj.push({
key: function(varsFromLibrary) {
funcs[j]();
}
});
}
Simply use let :
for (var i = 0; i < subArr.length; i++) {
let tmp = subArr[i];
mainObj.push({
key: function(varsFromLibrary) {
myFunc(tmp);
}
});
}
Or why cant you simply copy the value into the object?:
for (var i = 0; i < subArr.length; i++) {
mainObj.push({
tmp:subArr[i],
key: function(varsFromLibrary) {
myFunc(this.tmp);
}
});
}
Another try of explaining:
Lets imagine youre a byciclist. You want to measure your speed so you ask 10 friends of you to stand next to the route at certain points and to tell you your speed. Some pseudocode:
const friends = [];
var speed = 20;//youre really fast
for(var point = 1; point < 10; point++){
speed -= 2;//youre slowing down
friends.push({
ask(){
console.log(point, speed);
}
});
}
Now afterwards you stand at the last point 10 together with your friends and you ask them for the current speed and the point they stay at. What will they tell you? Exactly, they are all standing next to you at point 10 and your current speed is 0. You asked them for the current speed and not to remember the current speed. If you want them to remember it, they need to write it down:
friends.push({
speed,//every friend object has the current value stored
point,
ask(){ console.log(this.speed,this.point)}
});
Or you need to create 10 parallel universes your friends stay in, so if you ask them for your speed they will still see you driving next to them:
for(let point = 1; point < 10; point++){
let localspeed = (speed -= 2);//youre slowing down
why do i have to assign subArr[i] to tmp?
You don't. That isn't the solution proposed by the duplicate question.
Using myFunc(subArr[i]) will return that i is undefined?
i won't be undefined. It will be the same as subArr.length.
subArr[i] will be undefined, because subArr.length is the number of items in the array and the array is zero indexed.
why in myFunc i only receive the last value of subArr array?
Because that is the last value you copied to tmp before the loop ended.
As the high rated answer on the question you link to says, you need to copy i or subArr[i] to a new scope so it won't change next time you go around the loop.
consider this code
for (var i = 0; i < data.length; i++){
query.equalTo("objectId",data[i].id).first().then(
function(object){
object.set("username", data[i].username);
object.save();
}
);
}
in this example data[i] inside the then callback is the last element of the array
consider this 2nd example that normally work in javascript world
assume we use some API which connect to mongodb and has function called update
for (var i = 0; i < data.length; i++){
query.eq("_id",data[i].id).update(data[i].username);
}
eq returns the object, update updates that object and save it.
will it not be awesome if something like this is possible ... (not sure if it will also even work)
for (var i = 0; i < data.length; i++){
query.equalTo("objectId",data[i].id).first().then(
function(object, data[i]){
object.set("username", data.username);
object.save();
}
);
}
This actually doesn't work only because of scoping var. You can get the sample running as desired just by:
a) using let instead of var
b) creating a new scope for i (this is what let basically does). (In the anonymous fn, I used ii instead of i just for clarity. i would also work):
for (var i = 0; i < data.length; i++){
(function(ii) {
query.equalTo("objectId",data[ii].id).first().then(
function(object){
object.set("username", data[ii].username);
object.save();
}
);
})(i)
}
the best way to solve this problem with parse is to use recursivity
...
var do = function(entries, i){
if (entries[i]){
let user = data[i];
query.equalTo("objectId", user.id).first().then(
function(object){
object.set("username", user.username);
object.save();
}
).then(
function(){
return do(entries, i + 1);
}
);
}
}
I have vs.selectedTags which is an array with 3 objects.
In my for loop which will run 3 times, I need to make 3 API calls to get the tickers data for each object which I'm able too.
My problem comes when I try to assign those tickers to each vs.selectedTags[i].tickers object in the array.
It can't iterate over the i inside of the ApiFactory call. i becomes 3, and I have to cheat by using [i-1] to stop it from erroring out. However i still stays stuck at 2 so it always saves the last tickers data to all the items in my vs.selectedTags array.
var vs = $scope;
for (var i = 0; i < vs.selectedTags.length; i++) {
console.log(i);
vs.selectedTags[i].tickers = '';
console.log(vs.selectedTags[i].tickers);
ApiFactory.getTagData(vs.chosenTicker, vs.selectedTags[i].term_id).then(function(data) {
// console.log(data.data.ticker_tag);
console.log(data.data.ticker_tag.tickers);
console.log(i-1);
// console.log(vs.selectedTags[0]);
// How would you properly iterate [0 - 1 - 2] here?
vs.selectedTags[i-1].tickers = data.data.ticker_tag.tickers;
console.log(vs.selectedTags[i-1]);
});
}
You need a closure / new scope, as the ApiFactory.getTagData function is asynchronous
for (var i = 0; i < vs.selectedTags.length; i++) {
(function(j) {
vs.selectedTags[j].tickers = '';
ApiFactory.getTagData(vs.chosenTicker, vs.selectedTags[j].term_id).then(function(data) {
vs.selectedTags[j].tickers = data.data.ticker_tag.tickers;
});
})(i);
}
if you put the stuff inside of your for loop in a separate function it will fix your closure issue. so:
var bob = function(i){
console.log(i);
vs.selectedTags[i].tickers = '';
console.log(vs.selectedTags[i].tickers);
ApiFactory.getTagData(vs.chosenTicker, vs.selectedTags[i].term_id).then(function(data) {
// console.log(data.data.ticker_tag);
console.log(data.data.ticker_tag.tickers);
console.log(i);
// console.log(vs.selectedTags[0]);
// How would you properly iterate [0 - 1 - 2] here?
vs.selectedTags[i].tickers = data.data.ticker_tag.tickers;
console.log(vs.selectedTags[i]);
});
}
for (var i = 0; i < vs.selectedTags.length; i++) {
bob(i);
}
Quick bit about my background:
-been learning for about 3 months;
-work in tech support for a small software company. 2 years exp.
-a lot of knowledge is secondhand and I am still learning the basics
I am trying to create an object every second. The object is created directly to the last position of an array that remembers a set quantity of objects created before the most recent one
function Fruit(name, position) {
this.name = name;
this.position = position;
}
var showXMostRecentFruits = 20;
var fruitCounter = 0;
function generateName() {
var name = 'Experimental Fruit' + fruitCounter;
return name;
}
var fruitsArray = [];
function shiftFruits() {
for (i = 0; i < showXMostRecentFruits; i++) {
fruitsArray[i] = fruitsArray[i + 1];
}
function updateFruitPositions() {
for (i = 0; i < showXMostRecentFruits; i++) {
fruitsArray[i].position = i;
}
}
var fruitTimer; //used for setting and clearing setTimeout
function createNewFruit() {
shiftFruits();
fruitsArray[showXMostRecentFruits - 1] = new Fruit(generateName());
updateFruitPositions();
fruitCounter += 1;
fruitTimer = setTimeout(function() {
createNewFruit();
}, 1000);
}
Say the function createNewFruit() is run once
createNewFruit();
Then I try to pull some meaning from the array
console.log(fruitsArray[19];
All I get is:
Fruit {}
undefined
This issue is when I want to run a loop (see updateFruitPositions()) that updates a propery of each object in the array, an error is returned that the objects are undefined. I get that they are undefined because they are not assigned to unique variables (at least not that I'm aware of). How can I identify the objects or how can I create unique containers for them so I access them in the array?
You need to test whether a given element is set to something before attempting to write to one of its properties.
Instead of this...
for (i = 0; i < showXMostRecentFruits; i++) {
fruitsArray[i].position = i;
}
Use this:
for (i = 0; i < showXMostRecentFruits; i++) {
if (fruitsArray[i])
fruitsArray[i].position = i;
}
You fill the array from the end, staring with element 20. Without the if (fruitsArray[i]), you're attempting to set undefined.position = i for the first 19 elements.
You could replace the showFruits function with something much more efficient:
function shiftFruits() {
if (fruitsArray.length > showXMostRecentFruits) {
fruitsArray.shift();
}
}
and updateFruitPositions only needs to update members that exist, the length is controlled by shiftFruits:
function updateFruitPositions() {
for (i = 0; i < fruitsArray.length; i++) {
fruitsArray[i].position = i;
}
}
or where forEach is supported:
function updateFruitPositions() {
fruitsArray.forEach(function(fruit, i){fruit.position = i});
}
so it only visits members that exist. And the createNewFruit has:
fruitsArray.push(new Fruit(generateName());
I have an image map which has several divs on it as city points. And I wrote a class in css to animate those points' color, so I can add that class through jQuery, wait sometime and remove the class. The goal is to animate those points randomly (add class, wait, remove class at random), but currently I am stuck with waiting before removing the class. I tried different solutions, including those that are posted on this site, but no result. Hre is the code:
function builtCities() {
if ($('body.page-service-map').size()) {
var content = $('#region-content .content'),
cityDot = '<div class="city-dot"></div>',
cities = [
'moscow',
'saint-petersburg',
'krasnodar',
'rostov-na-donu',
'tyumen',
'omsk',
'irkutsk'
];
for (var i = 0; i < 7; i++) {
content.append(cityDot);
}
$('body.page-service-map .city-dot').each(function (index) {
$(this).addClass(cities[index]);
});
// animation
for (var j = 0; j < cities.length; j++) {
function partA(partB) {
$('.city-dot').eq(j).addClass('animate');
window.setTimeout(partB, 1000);
} partA(partB);
function partB() {
$('.city-dot').eq(j).removeClass('animate');
}
}
}
} builtCities();
It's not working because of closures. Do it like this:
for (var j = 0; j < cities.length; j++) {
$('.city-dot').eq(j).addClass('animate');
window.setTimeout((function (j) {
return function () {
$('.city-dot').eq(j).removeClass('animate');
};
}(j)), 1000);
}
Your current one doesn't work because your j variable will be persisted and will actually be equal to cities.length at the time you're calling partB. To get around this, the above calls a function passing in j which will return another function using a separate variable (the parameter j) that will use the correct index.