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);
}
);
}
}
Related
JSHint shows the error:
"Function declared within loop referencing an outer scope variable may lead to confusing semantics".
How can I improve the following code to get rid of the warning?
var getPrecedence = function getPrecedence(operator, operators) {
var keys = Object.keys(Object(operators));
for (var i = 0, len = keys.length; i < len; i++) {
var check = Object.keys(operators[keys[i]]).some(function (item) {
return item === operator;
});
if (check) return operators[keys[i]][operator];
}
};
You are supposed not to use the function expression inside the loop body, but instead declare it outside:
function getPrecedence(operator, operators) {
function isOperator(item) {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
return item === operator;
}
var keys = Object.keys(Object(operators));
for (var i = 0, len = keys.length; i < len; i++) {
var check = Object.keys(operators[keys[i]]).some(isOperator);
// ^^^^^^^^^^
if (check) return operators[keys[i]][operator];
}
}
Of course the whole thing could be simplified by just using includes instead of some, and find instead of the loop:
function getPrecedence(operator, operators) {
var keys = Object.keys(Object(operators));
var opkey = keys.find(key =>
Object.keys(operators[key]).includes(operator)
);
if (opkey) return operators[opkey][operator];
}
And finally, Object.keys(…).includes(…) can be simplified to operator in operators[key].
Add it before calling the function, this one will bypass that check
/* jshint -W083 */
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);
}
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);
}
}
At first blush, this looks like the same old 'closures in a loop' problem, but applying my usual solution is not actually solving the problem. Here's the code:
G.MultiToggle = function(each){
//data takes the form
//[{"data":(see Toggle), "onOpen":function(){}, "onClosed":function(){}},...]
this.children = [];
var which = null;
var toggles = [];
var that = this;
function makeOpenFn(j){
var info = each[j];
console.log(j);
return function(){
console.log(j);
info["onOpen"]();
if(which!=null){
toggles[which].close_up();
}
which = j;
};
};
function makeClosedFn(j){
var info = each[j];
console.log(j);
return function(){
console.log(j);
info["onClosed"]();
which = null;
};
};
function makeToggler(obj,opener,closer) {
return new G.Toggle(
obj.data,
opener,
closer
);
};
for(var i=0; i<each.length; i++){
var openFn = makeOpenFn(i);
var closedFn = makeClosedFn(i);
toggles[i] = makeToggler(each[i],openFn,closedFn);
toggles[i].close_up();
that.addChild(toggles[i]);
}
console.log(toggles);
}
G.MultiToggle.prototype = new createjs.Container();
The openFn and closedFn are used as event handlers by the toggle object later on. When they're invoked, they all spit out the results from i=2. I've tried moving the info variable declaration into the inner functions, and many other gymnastic permutations. I'm pulling my hair out, over here. Any help would be appreciated.
EDIT: Added more of the surrounding code for context.
It's the old closure in a loop problem. Only, you've missed a variable:
for(var i=0; i<each.length; i++){
openFn = makeOpenFn(i); //------ looks OK
closedFn = makeClosedFn(i); //-- looks OK
toggles[i] = function(){
return new G.Toggle(
each[i].data, //--------- AHA! closure in a loop!
openFn,
closedFn
);
}();
toggles[i].close_up();
that.addChild(toggles[i]);
}
You just need to break the closure to that i as well:
toggles[i] = function(new_i){
return new G.Toggle(
each[new_i].data,
openFn,
closedFn
);
}(i);
Or, if you prefer the style of the other functions:
function makeToggler(obj,opener,closer) {
return new G.Toggle(
obj.data,
opener,
closer
);
}
for(var i=0; i<each.length; i++){
openFn = makeOpenFn(i); //------ looks OK
closedFn = makeClosedFn(i); //-- looks OK
toggles[i] = makeToggler(each[i],openFn,closedFn);
toggles[i].close_up();
that.addChild(toggles[i]);
}
If you think there is problem inside the loop, I'll show you that the problem is not from your loop.
Let's make a simple loop that same as your loop to proof it:
function my(a){
return a+3;
}
for (var i=0; i<3; i++){
var ab = my(i);
var aa = function(){
alert(ab);
alert(i);
}();
}
I have a question to a specific behavior of javascript:
I have an object which I want to fill with generated functions. Each function contains a variable which is changed during the loop of function generation.
My problem is that the variable does not get replaced when assigning the function to the object. Instead the reference to the variable stays in the function and when executing the function only the last value of the variable is remembered.
Here is a minimal example (also on jsfiddle: http://jsfiddle.net/2FN6K/):
var obj = {};
for (var i = 0; i < 10; i++){
var nr = i;
obj[i] = function(){
console.log("result: " + nr);
}
}
for (var x = 0; x < 10; x++){
obj[x]();
}
The second loop executes all generated functions and all print a 9 as result. But i want that they print the value which the variable had at the time of generation (0, 1, 2, ...).
Is there a way to do this? Thanks in advance.
One approach is to call a function that returns a function:
function makeFunc(i) {
return function() {
console.log("result: " + i);
}
}
for (...) {
obj[i] = makeFunc(i);
}
Another approach is the immediately invoked function expression:
for (i = 0; ...; ...) {
(function(i) {
obj[i] = function() {
console.log("result: " + i);
}
})(i);
}
where in the latter case the (function(i) ... )(i) results in a permanent binding of i passed as a parameter to the outer function within the scope of the inner function
The problem is that all the functions you create are sharing a reference to the same nr variable. When you call them they fetch the value using that reference and therefore all of them get the same result.
Solve it like this:
for (var i = 0; i < 10; i++){
(function(nr) {
obj[nr] = function(){
console.log("result: " + nr);
}
})(i);
}
Your surmise is correct, and yes, there's a solution:
obj[i] = function( nr_copy ) {
return function() {
console.log("result: " + nr_copy);
};
}( nr );
JavaScript variables are scoped at the function level, unlike some other block-structured languages. That is, the fact that you declare "nr" inside the for loop doesn't make it "local" to that block — the effect is precisely the same as if you'd declared it at the top of the function.
By introducing another function scope with that anonymous function, you make a new copy of the value of "nr", which is then privately accessible to the actual function that's returned. Each of those functions will have it's own copy of the value of "nr" as it stood when that slot of the "obj" array was initialized.
what you want is to create a closure for every function you create.
Yet, the var(s) have not a block scope, so your code is the same as :
var obj = {};
var i;
var nr;
for (i = 0; i < 10; i++){
nr = i;
obj[i] = function(){
console.log("result: " + nr);
}
}
which hopefully makes it more obvious all functions will refer to the very same 'nr' var.
What you want to do implies creating a new scope each time, which might be done using bind, but let's stick to your original intent and build a new closure each time with a lambda :
var obj = {};
for (var i = 0; i < 10; i++) {
obj[i] = (function(){
var this_nr = i;
return function(){
console.log("result: " + this_nr);
}
}() );
}