$.get in a loop: why the function is performed after incrementing? - javascript

I am a beginner in Javascript and I feel that there is something wrong with me about the $.get jQuery.
Normally, you can assign it to a function that will execute after the data is retrieved correctly.
But if I put my $.get in a loop, the loop continues to execute even if the data is not yet retrieved, and here is my problem.
Here is my code (this is for GreaseMonkey):
var1 = document.getElementsByClassName("some_class");
i = 0;
while (i < var1.length) {
url = var1[i].getElementsByTagName("some_tag")[0].href;
$.get(url, function(data) {
if (data.contains("some_string")) {
alert(i);
}
});
i++;
}
Here, the alert returns var1.length event if it should returns 1 for exemple.
I try to put an alert(i) just after the url declaration and I understood that i++ was done before the function in my $.get.
This is surely a trivial problem, but I can not grasp the logic to not make this happen.

Wrap your $.get function thus:
(function(i) {
$.get(url, function(data) {
if (data.contains("some_string")) {
alert(i);
}
});
})(i);
The immediately invoked function expression causes the current value of i that's in the outer scope to be bound via the function's parameter i (which then hides the outer variable). If you like, give the function parameter a different name.
Note that this only fixes the problem you actually stated, which is that the loop variable is incremented independently of the callbacks. If you wish to ensure that the AJAX requests run one at a time then there are other solutions, e.g.:
var els = document.getElementsByClassName("some_class");
var i = 0;
(function loop() {
if (i < els.length) {
var el = els[i];
var url = el.getElementsByTagName("some_tag")[0].href;
$.get(url).done(function(data) {
if (data.contains("some_string")) {
alert(i);
}
i++;
}, loop); // .done(f1, f2) - see below
}
})();
The .done() call is in the form .done(callback, loop) and the two functions will be called in order. So the i++ line always happens first, and then it arranges for loop to be called pseudo-recursively to process the next element.

Since you're using jQuery, you can simplify your code quite a bit:
$('.some_class').each( function( i, element ) {
var url = $(element).find('some_tag')[0].href;
$.get( url, function( data ) {
if( data.contains("some_string") ) {
alert( i );
}
});
});
Changes from the original code are:
jQuery calls instead of the getElementsBy* functions.
jQuery .each() for the loop.
Added missing var where needed. (Very important in any version of the code!)
Note that the use of .each() automatically gives you the same effect as the immediately invoked function expression (IIFE) in another answer, but without the extra complication. That's because .each() always uses a callback function, and that creates the closure needed to preserve the i variable (and element too) uniquely for each iteration of the loop.
You can also do this when you have an ordinary while or for loop, and you still don't need the IIFE. Instead, simply call a function in the loop. Written this way, the code would be:
var $elements = $('.some_class');
for( var i = 0; i < $elements.length; i++ ) {
checkElement( i, $elements[i] );
}
function checkElement( i, element ) {
var url = $(element).find('some_tag')[0].href;
$.get( url, function( data ) {
if( data.contains("some_string") ) {
alert( i );
}
});
}
As you can see, the checkElement function is identical to the .each() callback function. In fact, .each() simply runs a similar for loop for you and calls the callback in exactly the same way as this code. Also, the for loop is more readable than the while loop because it puts all the loop variable manipulation in one place. (If you're not familiar with the for loop syntax it may seem less readable at first, but once you get used to it you will probably find that you prefer the for loop.)
In general, when tempted to use an IIFE in the middle of a loop, try breaking that code out into a completely separate function instead. In many cases it leads to more readable code.

Here's a little demo for you to investigate further.
$("#output").empty();
var startTime = new Date().getTime();
// try experimenting with async = true/false and the delay
// don't set async to false with too big a delay,
// and too high a count,
// or you could hang your browser for a while!
// When async==false, you will see each callback respond in order, followed by "Loop finished".
// When async==true, you could see anything, in any order.
var async = true;
var delay = 1;
var count = 5;
function createClosure(i) {
// return a function that can 'see' i.
// and i's remains pinned within this closure
return function (resp) {
var duration = new Date().getTime() - startTime;
$("#output").append("\n" + i + " returned: " + resp + " after " + duration + "ms");
};
}
for (var i = 0; i < count; i++) {
// jsfiddle url and params
var url = "/echo/html/";
var data = {
html: "hello " + i,
delay: delay
};
$.ajax(url, {
type: "post",
data: data,
async: async
}).then(createClosure(i));
}
var duration = new Date().getTime() - startTime;
$("#output").append("\n" + "Loop finished after " + duration + "ms");
Sample async=true output:
Loop finished after 7ms
0 returned: hello 0 after 1114ms
1 returned: hello 1 after 1196ms
2 returned: hello 2 after 1199ms
4 returned: hello 4 after 1223ms
3 returned: hello 3 after 1225ms
Sample async=false output (and the browser hangs for 5558ms!):
0 returned: hello 0 after 1113ms
1 returned: hello 1 after 2224ms
2 returned: hello 2 after 3329ms
3 returned: hello 3 after 4444ms
4 returned: hello 4 after 5558ms
Loop finished after 5558ms

Related

How can I make a for loop in Javascript that will set timeouts from an array?

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.

Unable to register event handler to array of elements in javascript

function recordData(){
var element = document.getElementsByClassName("bubble");
for(var i = 0; i < element.length; i++){
element[i].addEventListener("click", function(){
var id = element[i].attributes.id.value;
var x_cordinate = element[i].children[2].attributes.x.value;
var y_cordinate = element[i].children[2].attributes.y.value;
var keyword = element[i].children[0].textContent;
clicked_elements.push({
id: id,
x_cordinate: x_cordinate,
y_cordinate: y_cordinate,
keyword: keyword
})
}, false);
}
}
When i try to click the elements in html it shows an error "Uncaught TypeError: Cannot read property 'attributes' of undefined". I cannot access the id and other attributes of that particular element in addEventListener method. Please explain what i am doing wrong here if i need to record what is being clicked.
You can't use i inside the addEventListener callback because it will be executed after the end of the for loop.
Use this instead of elements[i] to get the current element inside the closure.
Your problem here is that the variable i won't have the value you think it'd have. That's due to how closures work.
Try this simple example:
for(var i = 0; i < 5; i++) {
console.log("Before timeout: " + i);
setTimeout(function() {
console.log("After timeout: " + i);
}, 1000);
}
What would you expect when you run this? Probably that after one second, you see this in the console?
Before timeout: 0
Before timeout: 1
Before timeout: 2
Before timeout: 3
Before timeout: 4
After timeout: 0
After timeout: 1
After timeout: 2
After timeout: 3
After timeout: 4
But guess what, you'll see this:
Before timeout: 0
Before timeout: 1
Before timeout: 2
Before timeout: 3
Before timeout: 4
After timeout: 5
After timeout: 5
After timeout: 5
After timeout: 5
After timeout: 5
Why 5? The for statement basically sets i to 0, then counts it up every iteration and checks every time whether it is still below 5, and if not, breaks the loop. So, after the loop, i has value 5, like this:
for(var i = 0; i < 5; i++) {
/* ... */
}
console.log("After loop: " + i);
...gives:
After loop: 5
Okay but why is the value used which i has after the loop? That's because your function callback you are passing to setTimeout accesses the i variable in the outside scope directly! And because the function will only run after one second (after the loop was long completed), the value of i at this time will be 5, as shown above!
So the solution would be to create a local copy of this variable which is local to each iteration. Now this is not as trivial as it seems because in JavaScript, control blocks like for don't create a scope (unless you use ES6 and let). You have to use another anonymous function around it to create a local copy which is then different each time:
for(var i = 0; i < 5; i++) {
// This is a so-called "immediately executed function expression"
// It's an anonymous function which is then immediately called due
// to the `()` at the end.
(function() {
var j = i; // Now we have a local `j`
console.log("Before timeout: " + j);
setTimeout(function() {
console.log("After timeout: " + j);
}, 1000);
})();
}
Another way to due this, which is a bit shorter, is this trick:
for(var i = 0; i < 5; i++) {
// See how this anonymous function now takes a parameter `i`?
// And we pass this parameter when calling this function below,
// which basically creates an inner copy which we can now also call
// `i`.
(function(i) {
console.log("Before timeout: " + i);
setTimeout(function() {
console.log("After timeout: " + i);
}, 1000);
})(i);
}
This is why I recommend to use things like forEach when iterating over arrays because they take a function callback as parameter so you automatically have a local scope in your code in there.
Now in your case, you could either use one of the anonymous-function-solutions from above, or something like forEach, but there is a gotcha: The element variable is no normal JavaScript array, it's a list of DOM elements, which doesn't have forEach. But you can still make it work using the Array.prototype.forEach.call trick:
function recordData(){
var element = document.getElementsByClassName("bubble");
Array.prototype.forEach.call(element, function(i) {
element[i].addEventListener("click", function(){
var id = element[i].attributes.id.value;
var x_cordinate = element[i].children[2].attributes.x.value;
var y_cordinate = element[i].children[2].attributes.y.value;
var keyword = element[i].children[0].textContent;
clicked_elements.push({
id: id,
x_cordinate: x_cordinate,
y_cordinate: y_cordinate,
keyword: keyword
})
}, false);
});
}
Before, it didn't work because you were effectively using element[element.length].attributes (because i was already one beyond your upper bound of the array), so element[i] is was undefined.
There you go!
However, an even better way would be using this because inside an event listener, this refers to the target of the event. So instead of element[i], you can just use this everywhere inside your listener!
You are running on a common issue with Javascript, in your 'for' you are registering the event handlers based on i value, when the event it's fired the value of 'i' is equals to 'element.length' that returns undefined this is the reason that you get your error.
To solve this you need to make a clousere in order to keep a reference to the element of the array not it's index. Try to use Array.forEach instead of a 'for'.
Here yoy con fin more info of how closures work.
function recordData(){
var element = document.getElementsByClassName("bubble");
for(var i = 0; i < element.length; i++){
element[i].addEventListener("click", function(){
var id = this.attributes.id.value;
var x_cordinate = this.children[2].attributes.x.value;
var y_cordinate = this.children[2].attributes.y.value;
var keyword = this.children[0].textContent;
clicked_elements.push({
id: id,
x_cordinate: x_cordinate,
y_cordinate: y_cordinate,
keyword: keyword
})
}, false);
}
}
This is the final version of my code which runs fine. 'this' helped me access the object inside the event listener.

Correct way to pass a variable argument in a callback in JavaScript?

I feel this should be answered somewhere in the internet but I failed to find it, maybe because I'm not searching the correct terms but this is the problem: I have the following function:
function ParentFunction (DataBase, Parameters) {
for (k = 0; k < DataBase.length; k++){
var CalendarURL = "https://www.google.com/calendar/feeds/" + DataBase.cid;
$.ajax({
url: CalendarURL,
dataType: 'json',
timeout: 3000,
success: function( data ) { succesFunction(data, k, Parameters);},
error: function( data ) { errorFunction ("Error",Parameters); }
});
}
}
I was getting errors in succesFunction(data, k, Parameters) because 'k' was always evaluated with the latest value. What is happening is that, when the for loop runs k is correctly increased but, when the callback function successFunction was executed, typically several ms after the loop was finished, it was always been evaluated with the last value of k, not the value of the loop the $.ajax was called.
I fixed this by creating another function that contains the ajax call. It looks like this:
function ParentFunction (DataBase, Parameters) {
for (k = 0; k < DataBase.length; k++){
var CalendarURL = "https://www.google.com/calendar/feeds/" + DataBase.cid;
AjaxCall(CalendarURL, k, Parameters);
}
}
function AjaxCall(URL, GroupIndex, Parameters) {
$.ajax({
url: URL,
dataType: 'json',
timeout: 3000,
success: function( data ) { succesFunction(data, GroupIndex, Parameters);},
error: function( data ) { errorFunction ("Error",Parameters); }
});
}
and it works. I think when the function is called in the parentFunction a copy of the value of the arguments is created and when the callback executes sees this value instead of the variable k which by the time would have a wrong value.
So my question is, is this the way to implement this behaviour? Or is there more appropriate way to do it? I worry that either, different browsers will act differently and make my solution work in some situations and not work in others.
You are hitting a common problem with javascript: var variables are function-scoped, not block-scoped. I'm going to use a simpler example, that reproduces the same problem:
for(var i = 0; i < 5; i++) {
setTimeout(function() { alert(i) }, 100 * i);
}
Intuitively, you would get alerts of 0 through 4, but in reality you get 5 of 5, because the i variable is shared by the whole function, instead of just the for block.
A possible solution is to make the for block a function instead:
for(var i = 0; i < 5; i++) {
(function(local_i) {
setTimeout(function() { alert(local_i); }, 100 * i);
})(i);
}
Not the prettiest or easier to read, though. Other solution is to create a separate function entirely:
for(var i = 0; i < 5; i++) {
scheduleAlert(i);
}
function scheduleAlert(i) {
setTimeout(function() { alert(i); }, 100 * i);
}
In the (hopefully near) future, when browsers start supporting ES6, we're going to be able to use let instead of var, which has the block-scoped semantics and won't lead to this kind of confusion.
Another option – rather than creating a new named function – would be to use a partial application.
Simply put, a partial application is a function that accepts a function that takes n arguments, and m arguments that should be partially applied, and returns a function that takes (n - m) arguments.
A simple implementation of a left-side partial application would be something like this:
var partial = (function() {
var slice = Array.prototype.slice;
return function(fn) {
var args = slice.call(arguments,1);
return function() {
return fn.apply(this, args.concat(slice.call(arguments)));
}
}
}).call();
With this, then you can take a function that requires two arguments like:
function add(a,b) { return a + b; }
Into a function that requires only one argument:
var increment = partial(add, 1);
increment(1); // 2
increment(10); // 11
Or even a function that requires no arugments:
var return10 = partial(add, 5, 5);
return10(); // 10
This is a simple left-side only partial application function, however underscore.js provides a version that can partially apply an argument anywhere in the argument list.
For your example, instead of calling AjaxCall() to create a stable variable scope, you could instead do:
function ParentFunction (DataBase, Parameters) {
for (k = 0; k < DataBase.length; k++){
var CalendarURL = "https://www.google.com/calendar/feeds/" + DataBase.cid;
var onSuccess = _.partial(succesFunction, _, k, Parameters);
$.ajax({
url: CalendarURL,
dataType: 'json',
timeout: 3000,
success: onSuccess,
error: function( data ) { errorFunction ("Error",Parameters); }
});
}
}
Here, we are using _.partial() to transform a function with a signature of:
function(data, index, params) { /* work */ }
into a signature of:
function(data) { /* work */ }
Which is the signature that the success callback will actually be invoked with.
Though admittedly, this is all pretty much just syntactical sugar for the same underlying concepts already described, it can sometimes conceptually help to think about problems like these from as functional perspective than procedural one.
This has to do with closures in javascript. Your anonymous functions each reference a variable outside of their current scope, so each function's "k" is bound to the original looping variable "k." Since these functions are called some time after, each function looks back to see that "k" is sitting at its last value.
The most common way to get around this is exactly what you did. Instead of using "k" in a nested function definition (which forces a closure), you pass it as an argument to an external function, where no closure is needed.
Here are a few posts with similar issues:
How do JavaScript closures work?
JavaScript closure inside loops – simple practical example
Javascript infamous Loop issue?

How to make a javascript FOR LOOP wait for certain conditions before looping

I have to call up a function (checkImdb) that will fetch some info from a php file (temp.php) and put some contents on a div (placeToFetchTo). This has to be done a certain amount of times, so I used a FOR LOOP for that.
The problem is that only the last instance of the looped counter (currentCastId) gets used. I understand there needs to be a way to force the FOR LOOP to wait for the fetch to be complete, and I have been looking online for answers but nothing seems to work so far. I apologise if I have missed an eventual answer that already exists.
Any help is appreciated.
This is the code I am referring to:
function checkImdb (totalCasts) {
$(function() {
for (currentCastId = 1; currentCastId <= totalCasts; currentCastId++) {
//Gets cast IMDB#
var row = document.getElementById("area2-" + currentCastId)
row = row.innerHTML.toString();
var fetchThis = "temp.php?id=" + row + "\ .filmo-category-section:first b a";
placeToFetchTo = "#area0-" + currentCastId;
function load_complete() {
var filhos = $(placeToFetchTo).children().length, newDiv ="";
var nrMoviesMissing = 0, looped = 0;
alert("done- "+ placeToFetchTo);
}
document.getElementById("area0").innerHTML = document.getElementById("area0").innerHTML + "<div id=\"area0-" + currentCastId + "\"></div>";
$(placeToFetchTo).load(fetchThis, null, load_complete);
} //End of: for (imdbLooper = 0; imdbLooper <= totalCasts; imdbLooper++) {
}); //End of: $(function() {
}
2017 update: The original answer had the callback arg as last arg in the function signature. However, now that the ES6 spread operator is a real thing, best practice is to put it first, not last, so that the spread operator can be used to capture "everything else".
You don't really want to use a for loop if you need to do any "waiting". Instead, use self-terminating recursion:
/**
* This is your async function that "does things" like
* calling a php file on the server through GET/POST and
* then deals with the data it gets back. After it's done,
* it calls the function that was passed as "callback" argument.
*/
function doAsynchronousStuff(callback, ...) {
//... your code goes here ...
// as final step, on the "next clock tick",
// call the "callback" function. This makes
// it a "new" call, giving the JS engine some
// time to slip in other important operations
// in its thread. This basically "unblocks"
// JS execution.
requestAnimationFrame(function() {
callback(/* with whatever args it needs */);
});
}
/**
* This is your "control" function, responsible
* for calling your actual worker function as
* many times as necessary. We give it a number that
* tells it how many times it should run, and a function
* handle that tells it what to call when it has done
* all its iterations.
*/
function runSeveralTimes(fnToCallWhenDone, howManyTimes) {
// if there are 0 times left to run, we don't run
// the operation code, but instead call the "We are done"
// function that was passed as second argument.
if (howManyTimes === 0) {
return fnToCallWhenDone();
}
// If we haven't returned, then howManyTimes is not
// zero. Run the real operational code once, and tell
// to run this control function when its code is done:
doAsynchronousStuff(function doThisWhenDone() {
// the "when done with the real code" function simply
// calls this control function with the "how many times?"
// value decremented by one. If we had to run 5 times,
// the next call will tell it to run 4 times, etc.
runSeveralTimes(fnToCallWhenDone, howManyTimes - 1);
}, ...);
}
In this code the doAsynchronousStuff function is your actual code.
The use of requestAnimationFrame is to ensure the call doesn't flood the callstack. Since the work is technically independent, we can schedule it to be called "on the next tick" instead.
The call chain is a bit like this:
// let's say we need to run 5 times
runSeveralTimes(5);
=> doAsynchronousStuff()
=> runSeveralTimes(5-1 = 4)
=> this is on a new tick, on a new stack, so
this actually happens as if a "new" call:
runSeveralTimes(4)
=> doAsynchronousStuff()
=> runSeveralTimes(4-1 = 3), on new stack
runSeveralTimes(3)
...
=> doAsynchronousStuff()
=> runSeveralTimes(1-1 = 0), on new stack
runSeveralTimes(0)
=> fnToCallWhenDone()
=> return
<end of call chain>
You need to use a while loop and have the loop exit only when all your fetches have completed.
function checkImdb (totalCasts) {
currentCastId = 1;
totalCasts = 3;
doneLoading = false;
while (!doneLoading)
{
//do something
currentCastId++;
if (currentCastId == totalCasts)
doneLoading = true;
}
}

How to pass variable to anonymous function

I want to pass variable setTimeoutfunction and do something with that. When I alert value of i it shows me numbers that i did not expected. What i m doing wrong? I want log values from 1 till 8.
var end=8;
for (var i = 1; i < end; i ++) {
setTimeout(function (i) {
console.log(i);
}, 800);
}
The standard way to solve this is to use a factory function:
var end=8;
for (var i = 1; i < end; i ++) {
setTimeout(makeResponder(i), 800);
}
function makeResponder(index) {
return function () {
console.log(index);
};
}
Live example | source
There, we call makeResponder in the loop, and it returns a function that closes over the argument passed into it (index) rather than the i variable. (Which is important. If you just removed the i argument from your anonymous function, your code would partially work, but all of the functions would see the value of i as of when they ran, not when they were initially scheduled; in your example, they'd all see 8.)
Update From your comments below:
...will it be correct if i call it in that way setTimeout(makeResponder(i),i*800);?
Yes, if your goal is to have each call occur roughly 800ms later than the last one, that will work:
Live example | source
I tried setTimeout(makeResponder(i),setInterval(i));function setInterval(index) { console.log(index*800); return index*800; } but it's not work properly
You don't use setInterval that way, and probably don't want to use it for this at all.
Further update: You've said below:
I need first iteration print 8 delay 8 sec, second iteration print 7 delay 7 sec ........print 2 delay 2 sec ...print 0 delay 0 sec.
You just apply the principles above again, using a second timeout:
var end=8;
for (var i = 1; i < end; i ++) {
setTimeout(makeResponder(i), i * 800);
}
function makeResponder(index) {
return function () {
var thisStart = new Date();
console.log("index = " + index + ", first function triggered");
setTimeout(function() {
console.log("index = " +
index +
", second function triggered after a further " +
(new Date() - thisStart) +
"ms delay");
}, index * 1000);
};
}
Live example | source
I think you now have all the tools you need to take this forward.
Your problem is that you are referring to the variable i some time later when your setTimeout() function fires and by then, the value of i has changed (it's gone to the end of the for loop. To keep each setTimeout with it's appropriate value of i, you have to capture that value i separately for each setTimeout() callback.
The previous answer using a factory function does that just fine, but I find self executing functions a little easier than factory functions to type and follow, but both can work because both capture the variables you want in a closure so you can reference their static value in the setTimeout callback.
Here's how a self executing function would work to solve this problem:
var end=8;
for (var i = 1; i < end; i ++) {
(function (index) {
setTimeout(function() {
console.log(index);
}, 800);
})(i);
}
To set the timeout delay in proportion to the value of i, you would do this:
var end=8;
for (var i = 1; i < end; i ++) {
(function (index) {
setTimeout(function() {
console.log(index);
}, index * 800);
})(i);
}
The self executing function is passed the value of i and the argument inside that function that contains that value is named index so you can refer to index to use the appropriate value.
Using let in ES6
With the ES6 of Javascript (released in 2015), you can use let in your for loop and it will create a new, separate variable for each iteration of the for loop. This is a more "modern" way to solve a problem like this:
const end = 8;
for (let i = 1; i < end; i++) { // use "let" in this line
setTimeout(function() {
console.log(i);
}, 800);
}
The main reason for this to not to work, is because, of the setTimeout which is set to run after 800 and the scope of i.
By the time it executes which the value of i will already have changed. Thus no definitive result could be received. Just like TJ said, the way to work this around is through a handler function.
function handler( var1) {
return function() {
console.log(var1);
}
}
var end = 8;
for (var i = 1; i < end; i++) {
setTimeout(handler(i), 800);
}
Demo
setTimeout accepts variables as additional arguments:
setTimeout(function(a, b, c) {
console.log(a, b, c);
}, 1000, 'a', 'b', 'c');
Source.
EDIT: In your example, the effective value of i will likely be 8, since the function is merely to be called after the loop has finished. You need to pass the current value of i for each call:
var end=8;
for (var i = 1; i < end; i ++) {
setTimeout(function (i) {
console.log(i);
}, 800, i);
}

Categories

Resources