I am trying to test some code and for that I need to make some tests in loop, like this:
for (var i = 1; i <= 5; i++) {
QUnit.test('Hello ' + i, (assert) => {
console.log(i);
assert.ok( 1 == '1', 'Result: ' + i);
});
}
Click here for working example at jsFiddle
But for some reason i in the loop (and result) is always 6, so this code gives me output like this:
6
6
6
6
6
What am I doing wrong?
Given that QUnit defines all tests prior to running them, you're a victim of the classic problem of var scope - vars are bound to the function, not to the for loop.
What this means is:
You define your test with a given value for i, but this value will have changed when the test is actually running.
You have a few ways around this:
Create an IIFE and define your test inside it
for (var i = 1; i <= 5; i++) {
(function (j) {
QUnit.test('Hello ' + j, (assert) => {
console.log(j);
assert.ok( 1 == '1', 'Result: ' + j);
});
})(i);
}
Why this works: the j variable above will be bound to the scope of that IIFE. Its value will not change when the test is run.
Use let keyword in an ES6 environment
for (let i = 1; i <= 5; i++) {
QUnit.test('Hello ' + i, (assert) => {
console.log(i);
assert.ok( 1 == '1', 'Result: ' + i);
});
}
Why this works: ES6 finally introduces block scoping, but this is done via the keywords let or const.
some parts of this answer corroborated from here
Related
I’m trying to use an increment loop but I want it to increment at the end of the loop. Sadly, whenever I simply put the i++ at the end of the loop it doesn’t behave like I’d expect or want it to. Anyone mind showing me the proper way of doing it?
The referred increment loop:
for (i = 1; i < 15; i++) {
// do somthing here
}
Here is the loop I’m working with:
for (i = 1; i < 15; i++) {
for (x = 1; x < 15; x++) {
var take = document.getElementById("row" + i + "sm" + x);
Tesseract.recognize(take)
.then(function(result) {
console.log(result.text);
// rows[i][x] = result.text;
})
}
}
What I’d like it to do:
for (i = 1; i < 15) {
for (x = 1; x < 15) {
var take = document.getElementById("row" + i + "sm" + x);
Tesseract.recognize(take)
.then(function(result) {
console.log(result.text);
//rows[i][x] = result.text;
x += 1;
})
i += 1;
}
}
I am using the for loop because I need to iterate over something one by one. How do I properly increment i at the end of the loop?
Here is a video explaining my problem with context and explanation why it is not an ASYNC problem. Sorry if it is hard to follow, ill update it with audio soon so I can explain it propperly.
https://drive.google.com/file/d/1n1ZwNJif5Lb5zfLb2GPpBemObwpOqNf7/view
The problem is that the second one doesnt wait until first one is complete.
You can try with recursion inside then. There maybe some mistake with i,x but you get the point.
You execute first with i=1 and x=1, after the operation is done (then) you call the next until all elements are executed.
function execItem(i, x) {
var take = document.getElementById("row" + i + "sm" + x);
Tesseract.recognize(take)
.then(function(result){
rows[i][x] = result.text;
if (i < 15 && x < 15) {
if (i > 15) {
x += 1
i = 1
} else {
i += 1
}
execItem(i, x)
}
})
}
execItem(1, 1)
As a comment suggests this actually seems likely to be a problem with an asynchronous call (Tesseract...then) inside a loop. By the time the function inside then is called, your values of x and i have already moved on, so you don't get the result you expect.
One way around this would be to use a 'closure' - making a function that creates another function based on the value of i and x.
function getDisplayFunc(row, col) {
function displayRecognisedText(result) {
console.log(row, col, result.text);
//rows[row][col] = result.text;
}
return displayRecognisedText;
}
for (i = 1; i < 15; i++) {
for (x = 1; x < 15; x++) {
var take = document.getElementById("row" + i + "sm" + x);
Tesseract.recognize(take).then(getDisplayFunc(i, x));
}
}
I guess #Mike spot the error on: your code is asynchronous. What does it mean?
So, let's suppose you have this loop:
for (i = 0; i < 10; i++) {
console.log(i);
}
It will print this, right?
0
1
2
3
4
5
6
7
8
9
However, you do not print your value inside the loop directly, but as a follow-up operation to a promise. This makes this code asynchronous. It means that it does not have to execute at the exact moment you call it. I do not have Tesseract here so I will make my loop asynchronous using another very old trick, setTimeout():
for (i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 0);
}
If I run it, I get this:
10
10
10
10
10
10
10
10
10
10
What happens is, when I pass the operation we want to do (in this case, printing the i value) to an asynchronous function (recognize().then() in your case, setTimeout() in my case) through a callback (function() {console.log(i);} in my example) the asynchronous function "schedules" the operation to execute as soon as possible, but this "soon" is not faster than the loop. So, the loop finishes executing but our callback is not called, not even once! Since you are not declaring i with let, it is a global variable, so there exists only one i. And since the loop finished, the value of the i variable is 10 already.
It used to be a hard thing to solve, but with ES6 it is quite straightforward: declare i with let!
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 0);
}
The let-ed variable has a new binding at each iteration of the loop, so in practice you have 10 variables called i. The closure of your function will have access only to the one with the right value!
Maybe you should try to use while loop.
Like this:
while i < 15:
//do something
i += 1
For two variables: x, i with embeding:
while x < 15:
//do something
while i < 15:
//do something2
i += 1
x += 1
Hope I understand the problem correctly.
A little while ago, I asked a question using this sample JS code...
for (var myindex = 0; myindex < mylist.length; myindex += 1) {
var copyindex = myindex;
MyAsync(mylist[myindex], function () { alert(copyindex); });
}
(The answer was essentially to call a named function, which keeps each variable in the loop separate.)
My question for today is, what's actually going on in this sample code above? Each alert call is being passed the same copyindex variable, even though a new one is being declared each time the loop goes around.
Is this behavior required by the JS language? (That by declaring a variable with block scope, I'm really declaring it at the top of the function.)
Or, is this perhaps an implementation detail of the few browsers I tested with, and future JS platforms are under no obligation to link the many instances of copyindex together?
Each alert call is being passed the same copyindex variable, even though a new one is being declared each time...
That's a common misunderstanding. var does not have block scope, so your code really is this:
var copyindex;
for (var myindex = 0; myindex < mylist.length; myindex += 1) {
copyindex = myindex;
MyAsync(mylist[myindex], function () { alert(copyindex); });
}
More (on my blog): Poor, misunderstood var and, of course, in the specification — it's covered in §10.5 - Declaration Binding Instantiation.
ES6 will introduce block-scoped variables via the let keyword. Where you use the let keyword matters quite a lot.
Let's take a simpler example, starting with var:
for (var x = 0; x < 3; ++x) {
setTimeout(function() { console.log("x = " + x); }, 0);
}
console.log("typeof x = " + typeof x);
We know that will give us
number
3
3
3
...because var is hoisted to the top of the scope, and so the same x is used by all three functions we create, and x exists outside the loop. (We see the typeof result first, of course, because the others happen after the minimal timeout.)
If we use let instead, in that same place:
for (let x = 0; x < 3; ++x) {
setTimeout(function() { console.log("x = " + x); }, 0);
}
console.log("typeof x = " + typeof x);
We get
undefined
3
3
3
...because x is scoped to the loop, not loop iterations. All three functions still use the same x, the only difference is that x doesn't exist outside the loop.
But if we use let within the body:
for (let n = 0; n < 3; ++n) {
let x = n;
setTimeout(function() { console.log("x = " + x); }, 0);
}
console.log("typeof x = " + typeof x);
We get
undefined
0
1
2
...because now x is scoped to the body of each iteration, not the loop as a whole.
You can try these yourself using NodeJS, just be sure to give it the --harmony flag to enable ES6 features.
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 8 years ago.
Im working with the gm npm module that deals with image manipulation. and i have this code.
for(i=0;i < 4;i++){
gm("www/img/" + image[i]).crop(550, 406, 0, 0).write(function(err) {
console.log(this.outname + " created :: " + arguments[3]); //success
});
}
this loop is meant to loop through the images array and crop each photo, but it only crops the last one. i think its something to do function invocation and callbacks, but not advanced yet for that level.
Change your code to:
for (var i = 0; i < 4; i++) {
(function (i) {
gm("www/img/" + image[i]).crop(550, 406, 0, 0).write(function(err) {
console.log(this.outname + " created :: " + arguments[3]); //success
});
}).call(this, i);
}
otherwise the value of i will be 3 each time your callback is being invoked.
You need to create a "closure" over the variable
Js has a function scope.
for (i = 0; i < 4; i++)
{
(function (a)
{
gm("www/img/" + image[a]).crop(550, 406, 0, 0).write(function (err)
{
console.log(this.outname + " created :: " + arguments[3]); //success
});
}).call(this,i)
}
or
that=this;
for (i = 0; i < 4; i++)
{
(function (a)
{
gm("www/img/" + image[a]).crop(550, 406, 0, 0).write(function (err)
{
console.log(that.outname + " created :: " + arguments[3]); //success
});
})(i)
}
edit :
Also - I would also keep a reference to the arguments since now , after IIFE - the arguments is changing.
you can keep your arguments via :
var args= Array.prototype.slice.apply(arguments)
example :
function g()
{
for (i = 0; i < 4; i++)
{
(function (a)
{
console.log(arguments); //huh ? arguments are not a,b,c !!! anymore
})(i);
}
}
g('a','b','c') // 0,1,2,3
so you do need to keep reference to the arguments cuz their changed after IIFE.
I have received this comment:
On the server side of Node.js when handling incoming data if you want to use a for loop you have to create i inside an anonymous function or you will pull your hair out wondering how the hell your variable i is greater than what you limit it to be inside your loop.
Here is the bugfix that was recommended:
var i = 0,
len = that.users.length;
(function(i) {
while(i < len) {
console.log(' - - - - - debug - - - - -');
console.log('i = ' + i );
i++;
}
})(i);
Can someone explain to me why using an anonymous function is necessary?
The problem only shows up when you have asynchronous code inside the loop. For example (I changed to a for loop for simplicity):
var i = 0,
len = that.users.length;
for(i = 0;i < len;i++) {
setTimeout(function() {
console.log('i = ' + i );
}, 500);
}
You will find that running this code causes the value of len to be printed len times, instead of a count up to len. This is because the for loop finishes before any of the print statements run, so the loop has exited because i == len.
The fix for this is to lock i in to each value with an Immediately-Invoked Function Expression (IIFE):
var i = 0,
len = that.users.length;
for(i = 0;i < len;i++) {
(function(i) {
setTimeout(function() {
console.log('i = ' + i );
}, 500);
})(i);
}
This isn't exactly the bugfix you presented, but it is the closest I can think of that makes much sense. Given more context, I could be more sure about what problem it is supposed to solve.
This is my code. What I want it to do is write 0, wait one sec, write 1, wait one sec, write 2, wait one sec, etc. Instead it writes 5 5 5 5 5
for(i = 0; i < 5; i++) {
setTimeout("document.write(i + ' ')", 1000);
}
http://jsfiddle.net/Xb7Eb/
1) You set all the timeouts to last 1 second at the same time. The loop doesn't wait for the timeout to occur. So you have 5 timeouts that all execute at the same time.
2) When the timeouts execute, the loop is long since complete and i has become 5. So once they execute, they all print "5"
3) document.write() writes somthing onto the page, in the same place it executes. I.e. if you have <script>document.write("xyz")</script> in the middle of a piece of text, it'll write "xyz" in the middle of the text. The timeouts, however, are not necessarily anywhere on the page. They exist only in code.
Here's a solution that's as close to yours as possible: http://jsfiddle.net/rvbtU/1/
var container = document.getElementById("counter");
for(i = 0; i < 5; i++) {
setTimeout("container.innerHTML += '" + i + " ';", 1000 * i);
}
However, that solution uses setTimeout's ability to evaluate a string as javascript, which is never a good idea.
Here's a solution that uses an anymous function instead: http://jsfiddle.net/YbPVX/1/
var container = document.getElementById("counter");
var writer = function(number) {
return function() { container.innerHTML += String(number) + " "; };
}
for(i = 0; i < 5; i++) {
setTimeout(writer(i), 1000 * i);
}
Edit: Forgot to save the 2nd fiddle. Whoops. Fixed now.
Most of the answers available are giving bad advice.* Specifically, you shouldn't be passing a string to setTimeout anymore (it still works, but it's discouraged), it's no longer 2000, there are better ways to do this.
setTimeout takes a function as the first parameter, and that's what you should do, however there are some issues when calling setTimeout in a loop.
This looks like it should work:
var i;
for ( i = 0; i < 5; i++ )
{
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i + 1) );
}
But it doesn't. The issue is that by the time setTimeout executes the function, the loop will have incremented i to 5, so you'll get the same value repeated.
There are a few fixes. If you're willing to risk a with statement, you could try the following:
var i;
for ( i = 0; i < 5; i++ )
{
with( { i:i } )
{
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i+1) );
}
}
Note that with is typically discouraged just like passing string values to setTimeout, so I don't really suggest this method of doing things.
The better way is to use a closure:
var i;
for ( i = 0; i < 5; i++ )
{
(function(i){
setTimeout(function(){
document.write( i + ' ' );
}, 1000 * (i+1) );
})(i);
}
To explain what's going on, the anonymous function wrapper (function(i){...code...}) executes immediately because it's wrapped in parens and passed i as a value:
(function(i){...code...})(i);
This forces the i variable that document.write uses to be a different one than what's being used in the for loop. You could even change the parameter used in the anonymous function wrapper if the difference gets too confusing:
(function(a){document.write(a+' ')})(i);
* when I started writing this question there were a number of answers describing how to fix the string to work with setTimeout, although they would technically work, they didn't include why they would work (because 'document.write("' + i + ' ");' evaluates i at the time of calling due to string concatenation, versus evaluating i at runtime like the previous version did), and they most certainly didn't mention that it's the bad old way of calling setTimeout.
try
var i = 1;
function timeout(){
document.write(i + ' ');
i++;
if (i == 5) return;
setTimeout(timeout, 1000);
}
timeout();
http://jsfiddle.net/nnJcG/1/
You have a problem with clousures, you can try this:
var timeout = function(){
var i = 0;
return function(){
document.write(i+ ' ');
i++;
if(i!==5)
setTimeout(timeout,1000);
};
}();
setTimeout(timeout,1000);
Here is the example in jsBin http://jsbin.com/uloyuc/edit
First of all, NEVER pass a string to setTimeout. Use a function, it's much cleaner.
Second, you have to "close over" the loop value. I bet this is what you want.
for(var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
document.write(i + ' ')
}, i * 1000);
}(i));
}
See more about you a self executing function to close over a loop value here http://www.mennovanslooten.nl/blog/post/62
And just cause I love it, here is the equivalent in CoffeeScript whihc has the do keyword to help out with just this case.
for i in [0..4]
do (i) ->
setTimeout ->
document.write "#{ i } "
, i * 1000
You can also work with setInterval and clearInterval:
var i = 0;
var f = setInterval(function() {
if(i == 4) clearInterval(f);
document.write(++i + ' ');
}, 1000);
I think this code is very readable.
You could try like this:
var tick_limit = 5; // Or any number you wish representing the number of ticks
var counter = 0; // Or any number you wish
var timer_interval = 1000; // Interval for the counter
var timer;
function timerTick()
{
if(counter < tick_limit)
{
// Execute code and increase current count
document.body.innerHTML+=(counter + ' '); // Append the counter value to the body of the HTML page
counter++;
timer = setTimeout(timerTick,timer_interval);
}
else
{
// Reset everything
clearTimeout(timer);
counter = 0;
}
}
function startCounter()
{
clearTimeout(timer); // Stop current timer
timer = setTimeout(timerTick,timer_interval); // Start timer with any interval you wish
}
...
// Start timer when required
startCounter();
...
This way, calling the startCounter a number of times will result in a single timer executing the code
You're triggering five timeouts at the same time.
I like Pindatjuh's answer, but here's another fun way to do it.
This way starts the next timeout when the previous one is finished:
// Wrap everything in a self executing anonymous function so we don't pollute
// the global namespace.
//
// Note: Always use "var" statments or you will pollute the global namespace!
// For example "for(i = 0; i < 5; i++)" will pollute the global namespace
// unless you have "var i; for(i = 0; i < 5; i++)" or
// "for(var i = 0; i < 5; i++)" & all of that is not in the global namespace.
//
(function() {
// "i" will be available within doThis()
// you could also pass "i" as an argument
var i = 0,
doThis = function() {
// setTimeout can take an anonymous function
// or a regular function. This is better than
// eval-ing a string.
setTimeout(function() {
document.write(i + ' ');
++i;
// Do the function again if necessary
if (i < 5) doThis();
}, 1000);
}
// Let's begin!
doThis();
})();
Working Example