I tried to get loop values inside knex function but I got final value of a loop.
for (i = 0; i < 10; i++) {
knex_in.raw(query).then(function (result) {
console.log(i)
});
}
Need help.
The counter in your loop (i) is a global variable. On each iteration of your loop, you are creating a promise. By the time your promises have resolved, the loop is complete, therefore i is the final value.
The solution is to save the value of i in a scoped variable. This can be done in a few ways, here are two:
You can use let (depending on ES6 support)
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 500);
}
Or you could store the value of i in a scoped variable by creating a function:
function someFunction(value) {
setTimeout(function() {
console.log(value);
}, 500);
}
for (i = 0; i < 10; i++) {
someFunction(i)
}
Related
I understand the behavior of using var and let in for loop in typescript/javascript but can someone explain why and how a const variable as a loop variable behaves ?
for (const i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 100 * i);
}
From what i understand , when you declare a variable as const and initialize its value , the value cannot be changed
Yet you can see the value being changed in the console.log() .An error has to be thrown while compilation right ?What am i missing here ?
I have created 2 examples for this behavior .
Loop variable as a const
Const variable re assignment
Can someone help me understand this ?
It works in Stackblitz because it is running traspiled code:
AppComponent.prototype.test = function () {
var _loop_1 = function (i) {
setTimeout(function () {
console.log(i);
}, 100 * i);
};
for (var i = 0; i < 5; i++) {
_loop_1(i);
}
};
It won't work if you add a snippet here because it is not transpiled
for (const i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i)
}, 100 * i);
}
Answering your question,
test(){
for(const i =0 ; i< 5; i++){
setTimeout(function(){
console.log(i)
},100*i);
}
}
This code essentially becomes,
test(){
// can be only initialized once
const i;
for(i = 0 ; i< 5; i++){
setTimeout(function(){
console.log(i)
},100*i);
}
}
Because every JavaScript variable is hoisted at the top of its scope, in this case the test() as its const variable that's why its hoisted in that block and not accessible outside of it.
To correct the piece of the code:
test(){
// can be only multiple times in that block
for(let i = 0 ; i< 5; i++){
setTimeout(function(){
console.log(i)
},100*i);
}
}
Which becomes,
test(){
let i;
// can be only multiple times in that block
for(i = 0 ; i< 5; i++){
setTimeout(function(){
console.log(i)
},100*i);
}
}
As both const and let have block scope and is hoisted at the top of the block its defined in, the only difference between const and let is variables declared const cannot be reinitialized.
I run the two following code snippets.
The first loop is giving the expected result when I assign a different name for the local variable.
for(var i = 0; i < 3; i++) {
setTimeout((function() {
var i2 = i;//named i2 here
return function(){console.log(i2)};
})(), 10);
}
The second loop will print undefined instead. I thought
var i = i will just re-declare the original i. And I expect it pint out some number.
How come I get undefined here?
for(var i = 0; i < 3; i++) {
setTimeout((function() {
var i = i;
console.log(i);
return function(){console.log(i)};
})(), 10);
}
The scope of the initialization expression of var is the body of the function, and the local variable i is already in that scope. So you can't reference an outer variable with the same name. You can think of
var x = <expression>;
as equivalent to:
var x;
x = <expression>;
If you look at it this way, you can see why var i = i; won't work, it's equivalent to:
var i;
i = i;
The assignment uses the uninitialized value of the local variable.
The usual idiom to solve this is to make i a parameter to the function, which you then pass in the argument list of the IIFE.
for(var i = 0; i < 3; i++) {
setTimeout((function(i) {
console.log(i);
return function(){console.log(i)};
})(i), 10);
}
For more information, see JavaScript closure inside loops – simple practical example
Scope
The inner var i declares a new variable called i within a scope that already has a variable called i.
The declaration is assigned the value of the scope's most prominent i, which is the one you just declared.
Since the value of the newly declared i is undefined, the assignation of itself to itself is of its value of undefined.
The variable i is already being used by the loop for in the outer scope. Now you are declaring a new variable i in an inner scope
var i = i;
Once that statement is ran the inner scope is the counter i of the loop is more accessible because you overridden it with new i.
Basically what you are doing here is: defining a new variable i and assign to it a value of that variable you just declared which is undefined.
Eaiset solution is to declare j and assign to it the value i.
for(var i = 0; i < 3; i++) {
setTimeout((function() {
var j = i;
console.log(j);
return function(){console.log(j)};
})(), 10);
}
OR just use i while passing it the function setTimeout
for(var i = 0; i < 3; i++) {
setTimeout((function(i) {
console.log(i);
return function(){console.log(i)};
})(), 10);
}
My guess is it should be return function(){console.log(i)}; in the second snippet. And the reason it still does not work is, that you're basically assigning to var i the same i you just declared with var. You'll see what I mean if you split declaration and assignment:
var i;
i = i;
But yeah, I can see how js quirks like that can be quite frustrating, because one has to always watch out and reinvent new variable names as one goes down the call stack. This is one reason why I fell in love with TypeScript, because it compiles this:
for (let i = 0; i < 3; ++i) {
window.setTimeout(() => {
console.log(i);
});
}
into this:
var _loop_1 = function (i) {
window.setTimeout(function () {
console.log(i);
});
};
for (var i = 0; i < 3; ++i) {
_loop_1(i);
}
which will yield the expected result.
a = [];
for (var i = 0; i < 3; i++) {
a.push(function() {
console.log(i);
})
}
a[0]() // I want 0, but I get 3
I am trying to write a simple piece of code where I have an array of functions such that when I execute a function at a particular index, the index value should get printed.
However, the piece of code above shows the same result (3 in this case) for all index values. I understand that the value is pointing by reference and therefore points to the last value of i. Could someone point out how to do this in the right manner?
Wrap it around a function. Now each time the loop executes, the wrapper function has its own value of i.
a = [];
for (var i = 0; i < 3; i++) {
(function(i){
a.push(function() {
console.log(i);
})
})(i);
}
a[0]()
You can add a self executing function to act like a module. Doing this, the scope of the variable i is in that function.
a = [];
for (var i = 0; i < 3; i++) {
(function(i){
a.push(function() {
alert(i);
})
})(i);
}
a[0]()
Note: In this block (function(i){ ... })(i), i can have any name, there is no connection between i from loop and i from function, i.e. (function(r){ ... })(r).
Here is an alternate version to an anonymous function that gets created and executed all at once.
The issue that you are having is that when the function gets called, the loop has already been evaluated and the value of i has already reached the max value of 3. You need trap the current value of i while the loop is being evaluated.
var a = [];
for (var i = 0; i < 3; i++) {
var fn = function() {
console.log(arguments.callee.i);
}
fn.i = i; // Pass as 'i' parameter to 'fn'.
a.push(fn);
}
a[0](); // The value 0 will be printed, rather than 3.
There is more than one way to skin a cat. The code above is very similar to:
var a = [];
function fn(i) {
return function() {
console.log(i);
}
}
for (var i = 0; i < 3; i++) {
a.push(fn(i));
}
a[0](); // The value 0 will be printed, rather than 3.
I'm trying to make a few functions to work one after the other with a waiting time of 1.5 seconds between them.
NOW, when i try doing so with the same Id (Inside the "NoteList(>here<)", like 1, 2, 3, or any other, it works;
for (var i = 0; i < 36; i++)
{
setTimeout(function () { OnClcRandom(NoteList[0]) }, i * 1000 + 1000);
}
BUT! when i try doing so with the var i, it doesn't work and gets the all of the functions in the page stuck. any idea why?
for (var i = 0; i < 36; i++)
{
setTimeout(function () { OnClcRandom(NoteList[i]) }, i * 1000 + 1000);
}
That would be because all of the functions refer to the same live i variable, not the value of the variable at the time you called setTimeout(). Which means by the time the timeouts actually run your function i will be 36.
Try this instead:
for (var i = 0; i < 36; i++) {
(function(x){
setTimeout(function () { OnClcRandom(NoteList[x]) }, i * 1000 + 1000);
)(i);
}
This executes an anonymous function on each iteration of the loop, with each execution getting its own x parameter for use in your original function.
Javascript doesn't create local scope for block. :)
And in your second example var i equal 36 (last value).
You need create local scope inside loop.
for (var i = 0; i < 36; i++) {
(function (i) {
setTimeout(.......);
}(i))
}
You also may fixed 'i' value assign it to function property:
for (var i = 0, f; i < 36; i++){
f = function _callback() { var i = _callback.i; .....};
f.i = i;
setTimeout(f, i * 1000);
}
Consider such loop:
for(var it = 0; it < 2; it++)
{
setTimeout(function() {
alert(it);
}, 1);
}
The output is:
=> 2
=> 2
I would like it to be: 0, 1. I see two ways to fix it:
Solution # 1.
This one based on the fact that we can pass data to setTimeout.
for(var it = 0; it < 2; it++)
{
setTimeout(function(data) {
alert(data);
}, 1, it);
}
Solution # 2.
function foo(data)
{
setTimeout(function() {
alert(data);
}, 1);
}
for(var it = 0; it < 2; it++)
{
foo(it);
}
Are there any other alternatives?
Not really anything more than the two ways that you have proposed, but here's another
for(var it = 0; it < 2; it++)
{
(function() {
var m = it;
setTimeout(function() {
alert(m);
}, 1);
})();
}
Essentially, you need to capture the variable value in a closure. This method uses an immediately invoked anonymous function to capture the outer variable value it in a local variable m.
Here's a Working Demo to play with. add /edit to the URL to see the code
With the let keyword you can get around this completely:
for(let it = 0; it < 2; it++)
{
setTimeout(function() {
alert(it);
}, 1);
}
Similar to above solution but self invoking inside of setTimeout function
for(var it = 0; it < 2; it++)
{
setTimeout(function(cur) {
return function(){
alert(cur);
};
}(it), 1);
}
Similar to the other solutions, but in my opinion cleaner:
for (var it = 0; it < 2; it++) {
// Capture the value of "it" for closure use
(function(it) {
setTimeout(function() {
alert(it);
}, 1);
// End variable captured code
})(it)
}
This keeps the same variable name for the capture, and does it for the entire loop, separating that from the logic of the timeout setup. If you want to add more logic inside the block, you can trivially do that.
The only thing I don't like about the solution is the repeat of "it" at the end.