I have the following code (dbclient is Redis client for Node.js):
dbclient.zrange("cache", -1000000000000000, +1000000000000000, function(err, replies) {
logger.info("Replies: " + replies.length);
logger.info(err);
for (var i = 0; i < replies.length; i++) {
logger.info("I: " + i)
dbclient.hmget("doc:" + replies[i], "size", function(err, res) {
cache.set(replies[i], parseInt(res[0]));
logger.info(res[0]);
});
}
});
I notice a strange behavior:
The first output is: Replies: 195748, but in the for loop I notice that it always prints I: 195747 and the res[0] is always 198536. This run 195747 times.
It seems that it's stuck on the last index, and doesn't iterate over all items.
This is one of the most common errors in Javascript, in
function(err,res){
cache.set(replies[i], parseInt(res[0]));
logger.info(res[0])
}
you are using i, but that is always the one from the parent scope. Since the function is run as a callback asynchronously, it is always the value of the last loop iteration.
Change it to
(function(i) {
return function(err,res){
cache.set(replies[i], parseInt(res[0]));
logger.info(res[0])
};
})(i)
to bind i to the inner-most function's scope
further explanation: JavaScript closure inside loops – simple practical example
Related
Background (You might want to skip this)
I'm working on a web app that animates the articulation of English phonemes, while playing the sound. It's based on the Interactive Sagittal Section by Daniel Currie Hall, and a first attempt can be found here.
For the next version, I want each phoneme to have it's own animation timings, which are defined in an array, which in turn, is included in an object variable.
For the sake of simplicity for this post, I have moved the timing array variable from the object into the function.
Problem
I set up a for loop that I thought would reference the index i and array t to set the milliseconds for each setTimeout.
function animateSam() {
var t = [0, 1000, 2000, 3000, 4000];
var key = "key_0";
for (var i = 0; i < t.length; i++) {
setTimeout(function() {
console.log(i);
key = "key_" + i.toString();
console.log(key);
//do stuff here
}, t[i]);
}
}
animateSam()
However, it seems the milliseconds are set by whatever i happens to be when the function gets to the top of the stack.
Question: Is there a reliable way to set the milliseconds from the array?
The for ends before the setTimeout function has finished, so you have to set the timeout inside a closure:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
for (var i = 0; i < t.length; i++) {
(function(index) {
setTimeout(function() {
alert (index);
key = "key_" + index.toString();
alert (key);
//do stuff here
}, t[index]);
})(i);
}
}
Here you have the explanation of why is this happening:
https://hackernoon.com/how-to-use-javascript-closures-with-confidence-85cd1f841a6b
The for loop will loop all elements before the first setTimeout is triggered because of its asynchronous nature. By the time your loop runs, i will be equal to 5. Therefore, you get the same output five times.
You could use a method from the Array class, for example .forEach:
This ensures that the function is enclosed.
[0, 1000, 2000, 3000, 4000].forEach((t, i) => {
setTimeout(function() {
console.log(i);
console.log(`key_${i}`);
//do stuff here
}, t)
});
Side note: I would advise you not to use alert while working/debugging as it is honestly quite confusing and annoying to work with. Best is to use a simple console.log.
Some more clarifications on the code:
.forEach takes in as primary argument the callback function to run on each of element. This callback can itself take two arguments (in our previous code t was the current element's value and i the current element's index in the array):
Array.forEach(function(value, index) {
});
But you can use the arrow function syntax, instead of defining the callback with function(e,i) { ... } you define it with: (e,i) => { ... }. That's all! Then the code will look like:
Array.forEach((value,index) => {
});
This syntax is a shorter way of defining your callback. There are some differences though.
I would suggest using a function closure as follows:
function animateSam(phoneme) {
var t = [0,1000,2000,3000,4000];
var handleAnimation = function (idx) {
return function() {
alert(idx);
key = "key_" + idx.toString();
alert(key);
//do stuff here
};
}
for (var i = 0; i < t.length; i++) {
setTimeout(handleAnimation(i), t[i]);
}
}
I this example you wrap the actual function in a wrapper function which captures the variable and passes on the value.
I have this code, it's independent and isolated. The problem I am having is that the index i is starting at 1 instead of starting at 0. I have no idea why this could be, and doesn't seem to have anything to do with the closure that I am pushing into the deletes array...but I can't be sure, no idea what the issue is.
onClickResetAll: function (event) {
event.preventDefault();
var deletes = [];
Object.keys(collections).forEach(function (key) {
if (collections.hasOwnProperty(key)) {
var coll = collections[key];
for (var i = 0; i < coll.models.length; i++) {
deletes.push(function (callback) {
var index = i; //i starts at 1, not 0 !!!
coll.models[index].deleteModel(function (err, resp, x) {
console.log(err, resp, x);
if(err){
callback(err);
}
else{
callback(null,null);
}
});
});
}
}
});
async.parallel(deletes,function(err,results){
Backbone.Events.trigger('bootRouter', '+refreshCurrentPage');
});
}, //end of onClickResetAll callback function
//end
The problem isn't really that i starts at one, the problem is that i will be coll.models.length for every function in deletes. Why would that be? Well, each function is sharing the same i and i won't be evaluated until the functions inside deletes are actually called.
The solution is to force i to be evaluated when it has the value you want (i.e. evaluated i when you're building the callback function). There are various solutions and they're all variations on the "wrap it in a function to break the reference" theme:
Use an iterator with a callback function instead of a plain for loop:
coll.each(function(model, i) {
// `model` is the model from the collection, `i` is the loop index.
});
You can use each here because Backbone collections have a bunch of Underscore functions built in.
Wrap the loop body in an SIF:
for(var i = 0; i < coll.models.length; ++i)
(function(i) {
//...
})(i);
Use a separate function to build your functions:
function make_deleter(coll, i) {
return function(callback) {
coll.models[i].deletedModel(function(err, resp, x) {
//...
}
}
}
//...
for(var i = 0; i < coll.models.length; ++i)
deletes.push(make_deleter(coll, i));
They all do pretty much the same thing: add an extra function call into the mix to force i to be evaluated (rather than just referenced) on each iteration of the loop.
In a Backbone situation, 1 would probably be the most natural and you wouldn't even need your troublesome i with that approach.
another solution to this is using async.each or async.eachSeries instead of async.parallel. using the first 2 avoids pushing to an array of functions altogether.
I've found some example in a tutorial (said it was the canonical example)
for (var i=1; i<=5 ; i++) {
setTimeout(function() {
console.log("i: " + i);
}, i*1000);
}
Now, I understand that, closure passes in the current scope in to the function, and I assume that it should output 1,2,3,4,5. But instead, it prints number 6 five times.
I ran it in the chrome debugger, and first it goes through the loop without going in to the function while doing the increment of the i value and only after that, it goes in to the inner function and execute it 5 times.
I'm not sure why its doing that, I know, the current scoped is passed in to the function because of closure, but why does it not execute each time the loop iterate?
By the time the timeout runs, the for loop has finished, and i is 6, that's why you're getting the output you see. You need to capture i during the loop:
for (var i=1; i<=5 ; i++) {
(function(innerI) {
setTimeout(function() {
console.log("i: " + innerI);
}, innerI*1000);
})(i);
}
This creates an inner function with it's own parameter (innerI), that gets invoked immediately and so captures the value of i for use within the timeout.
If you didn't want the complex-looking IIFE as explained in James' answer, you can also separate out the function using bind:
function count(i) {
console.log("i: " + i);
}
for (var i = 1; i <= 5; i++) {
setTimeout(count.bind(this, i), i * 1000);
}
Thank you for you help,
I found out another solution and it was a minor change.
On the top of the page I turned on the strict mode and also in the for loop, Instead of var, I used the "let" keyword.
The code might look more complicated than it needs to be I want to pass i into the balm function but it returns undefined because I'm doing it wrong.
Just extra information: This is for the server of a game I'm writing. Running in console for node.
for (i=30;i>=0;i--){
setTimeout(function balm(i){
this_sql ="UPDATE game_moblist SET hp = least(max_hp, hp +"+Math.round(i/2)+") WHERE id ="+mobid
connection.query(this_sql, function(err, rows, fields) {if (err) err=null});
console.log(this_sql)
this_sql ="SELECT hp, max_hp FROM game_moblist WHERE id ="+mobid; //emite catch all update pointint to hp..
connection.query(this_sql, function(err, rows, fields) {if (err) throw err;
socket.emit ('updatemisc',handler,rows);//eval handler using args
if (rows[0].hp==rows[0].max_hp){
i=0;
return i;
}
});
},(31-i)*333);
}
here is a simplified version just showing the concept
for (i=3;i>=0;i--){
setTimeout(function foo(i){
console.log(foo)
},1000*i);
I would expect the following output
"1" after 1000 ms
"2" after 2000 ms
"3" after 3000 ms
EDIT: It worked when I defined the function outside of setTimeout() and then called it like this
setTimeout(balm(i),...
You can't use a loop variable i declared outside the callback function and expect it to have the right value once the callback is actually executed - it'll have the last value assigned to it instead.
The code below shows the simplest (but not the shortest) solution:
function showNumber(n) {
return function() {
console.log(n);
}
}
for (i = 3; i >= 0; i--) {
setTimeout(showNumber(i), 1000 * i);
}
In other words, you call a function (that has its parameter "bound" to your loop variable) which then returns another function that is the one actually invoked by setTimeout().
There are other ways of doing this, typically with an Immediately Invoked Function Expression as shown in #Xander's answer, but the code above demonstrates the solution nicely.
i is 0 when the first callback executes and stays that way through the rest of the calls.
You could create a closure to capture the value of i at the moment of declaration:
for (i = 3; i >= 0; i--){
function(x) {
setTimeout(function foo(i){
console.log(i)
},1000 * x);
})(i);
}
Variables can not be passed into a function in its declaration.
for (i=3; i>=0; i--) {
fooLoop(i);
}
function fooLoop(iterator) {
setTimeout(function () {
console.log("value of i is" + iterator);
}, 1000*iterator);
}
I am sure this is a basic question, but I have been searching google for awhile and can't find a satisfactory answer..
I am used to programming MySQL select queries in PHP and simply grabbing a result, looping through each row, and within the loop doing further queries based on the column values of each individual row.
However, I'm working with javascript server side code now that relies on a SQL object where you pass the query and then a callback function that will be invoked after the query is run.
I'm confused with some of the scoping issues and how to best cleanly do this. For example, I don't want to do something like:
SQL.query("select * from blah", function(result) {
for(var i = 0; i < result.length; i++) {
SQL.query("select * from blah2 where i =" + result[i].property, function(result2) {
//now how do I access result from here? I know this isn't scoped correctly
});
}
});
What is the standard way to write this style of nested SQL query and not have scoping issues/messy code? Thanks!
This is very interesting... I've never heard of "server-side javascript"... but none the less this might help organize your code a bit. I use this method to organize my ajax request callbacks.
using your example it would look like this.
SQL.query("select * from some_table", function(result){ runNestedQuery(result); });
function runNestedQuery(result){
for(var i = 0; i < result.length; i++) {
SQL.query("select * from blah2 where i =" + result[i].property, function(result2){ nestedResult(result2); });
}
}
There are no scoping issues with your above code - but this is a nice way I like to organize this kind of thing.
result will be available in the second callback, that's how closures in JavaScript work, the functions has access to all variables in the outer scopes it was defined in.
function outer() {
var foo = 1;
function inner() { // inherits the scope of outer
var bla = 2;
console.log(foo); // works!
// another function in here well inherit both the scope of inner AND outer, and so on
}
inner();
console.log(bla); // doesn't work, raises "ReferenceError: bla is not defined"
}
outer();
Now, on to the problem, i will not point to the correct value, it too will be inherited to the second callback but it`s a reference and will therefore has the wrong value.
Fix is to create another closure:
SQL.query("select * from blah", function(result) {
for(var i = 0; i < result.length; i++) {
(function(innerResult) { // anonymous function to provide yet another scope
SQL.query("select * from blah2 where i =" + innerResult.property, function(result2) {
// innerResult has the correct value
});
})(result[i]); // pass the current result into the function
}
});
Or an extra function:
function resultThingy(result) {
SQL.query("select * from blah2 where i =" + result.property, function(result2) {
// result has the correct value
});
}
SQL.query("select * from blah", function(result) {
for(var i = 0; i < result.length; i++) {
resultThingy(result[i]);
}
});
Since you are using server-side Javascript, you can likely use forEach. Assuming that result instanceof Array == true:
SQL.query("select * from blah", function(result) {
result.forEach(function(item, index) {
SQL.query("select * from blah2 where i = " + item.property, function(result2) {
console.log(item, index, result); //works as intended
});
});
});
If result is merely array-like, then this
Array.prototype.forEach.call(result, function(item, index) { // etc...
should do the trick.
As others have pointed out result actually will be available all the way down in the nested callback.
But there is a very tricky part to this:
...Because the nested query runs asynchronously, your code will actually fire off a bunch of parallel queries -- one for each row in result -- all running at the same time (!). This is almost certainly not what you want; and unless result is very small indeed, all the simultaneous queries will use up all your available db connections rather quickly.
To remedy this, you might use something like this:
SQL.query("select * from blah", function(result) {
handleBlahRow( result, 0 );
});
function handleBlahRow( result, i ) {
if( !result || (i >= result.length)) return;
SQL.query("select * from blah2 where i =" + result[i].property, function(result2) {
// kick off the next query
handleBlahRow( result, i+1 );
// result, i, *and* result2 are all accessible here.
// do whatever you need to do with them
});
});
The above will run your nested queries 1-at-a-time. It's fairly easy to adapt the above to introduce limited parallelism (eg. 4-at-a-time), if you want it -- though it's probably not necessary.