Javascript: Using changing global variables in setTimeout - javascript

I am working on Javascript and using firefox scratchpad for executing it. I have a global index which I want to fetch inside my setTimeout (or any function executed asynchronously). I can't use Array.push as the order of data must remain as if it is executed sequentially. Here is my code:-
function Demo() {
this.arr = [];
this.counter = 0;
this.setMember = function() {
var self = this;
for(; this.counter < 10; this.counter++){
var index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
}
};
this.logMember = function() {
console.log(this.arr);
};
}
var d = new Demo();
d.setMember();
setTimeout(function(){
d.logMember();
}, 1000);
Here, I wanted my d.arr to have 0 - 9 indexes, all having 'I am John!', but only 9th index is having 'I am John!'. I thought, saving this.counter into index local variable will take a snapshot of this.counter. Can anybody please help me understand whats wrong with my code?

The problem in this case has to do with scoping in JS.
Since there is no block scope, it's basically equivalent to
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
}
};
Of course, since the assignment is asynchronous, the loop will run to completion, setting index to 9. Then the function will execute 10 times after 100ms.
There are several ways you can do this:
IIFE (Immediately invoked function expression) + closure
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout((function (i) {
return function(){
self.arr[i] = 'I am John!';
}
})(index), 100);
}
};
Here we create an anonymous function, immediately call it with the index, which then returns a function which will do the assignment. The current value of index is saved as i in the closure scope and the assignment is correct
Similar to 1 but using a separate method
this.createAssignmentCallback = function (index) {
var self = this;
return function () {
self.arr[index] = 'I am John!';
};
};
this.setMember = function() {
var self = this;
var index;
for(; this.counter < 10; this.counter++){
index = this.counter;
setTimeout(this.createAssignmentCallback(index), 100);
}
};
Using Function.prototype.bind
this.setMember = function() {
for(; this.counter < 10; this.counter++){
setTimeout(function(i){
this.arr[i] = 'I am John!';
}.bind(this, this.counter), 100);
}
};
Since all we care about is getting the right kind of i into the function, we can make use of the second argument of bind, which partially applies a function to make sure it will be called with the current index later. We can also get rid of the self = this line since we can directly bind the this value of the function called. We can of course also get rid of the index variable and use this.counter directly, making it even more concise.
Personally I think the third solution is the best.
It's short, elegant, and does exactly what we need.
Everything else is more a hack to accomplish things the language did not support at the time.
Since we have bind, there is no better way to solve this.

The setTimeout doesn't have a snapshot of index as you are expecting. All of the timeouts will see the index as the final iteration because your loop completes before the timeouts fire. You can wrap it in a closure and pass index in, which means the index in the closure is protected from any changes to the global index.
(function(index){
setTimeout(function(){
self.arr[index] = 'I am John!';
}, 100);
})(index);

The reason is that by the time settimeout is started, the for loop is finished executing the index value is 9 so all the timers are basically setting the arr[9].

The previous answer is correct but the source code provided is wrong, there is a mistyping elf in place of self . The solutions works.
An other way , without a closure , is to just add the index parameter to the function declaration in the setTimeout statement
function Demo() {
this.arr = new Array();
this.counter = 0;
this.setMember = function() {
var self = this;
for(; this.counter < 10; this.counter++){
var index = this.counter;
setTimeout(function(){
self.arr[index] = 'I am John!';
}(index), 100);
}
};
this.logMember = function() {
console.log(this.arr);
};
}
var d = new Demo();
d.setMember();
setTimeout(function(){
d.logMember();
}, 1000);

Related

Counting function

I need to have multiple counters so I created a function, the problem is that I can't store the value in the variable outside the function. I thought this would be easy but I couldn't find anything online.
myCounter = 2;
function count(counter) {
counter++;;
}
count(myCounter);
I would get the value in myCounter to update every time I call the function, thanks for any help you can provide
You can use a closure function that returns an object with a getter so you can initialize as many individual instances as you need
function counter() {
let _count = 0;
return {
get count() {
return ++_count;
}
}
}
let ct1 = new counter();
console.log('ct1', ct1.count, ct1.count)
let ct2 = new counter();
console.log('ct2', ct2.count, ct2.count, ct2.count)
Try this.
var myCounter = 2;
var myCounter2 = 3;
function count(counter) {
return ++counter;
}
myCounter = count(myCounter);
myCounter2 = count(myCounter2);
console.log('myCounter: ' + myCounter);
console.log('myCounter2: ' + myCounter2);

Add delay between 'for' loop iterations

I'm trying to implement basic 60 sec counter(A p element with idcounter), that is triggered after a button(counter_start()) is pressed.But I want delay of 1 sec between this and make sure this updates in browser window in real-time
<script type="text/javascript">
function counter_start(){
x=0
for(i=0;i<60;i++){
x++;
document.getElementById("counter").innerHTML=x;
}
}
</script>
P.S: There might be other simple methods of implementing a timer.But it's not about timer...actually I'm a student and trying to figure out the architecture and mechanism of this.
EDIT: please post tested versions of the code, as some of em' posted below DO NOT update in real time
Try this Example
Hope it will work for u
JS
for(i = 1; i <= 3; i++)
{
(function(i){
setTimeout(function(){
alert(i);
}, 1000 * i);
}(i));
}
Javascript operates synchronously in the browser.
You need to use setTimeout or setInterval to schedule the for loop's body to be called every second. I'm using setTimeout in the below example for easier "garbage collection"; we will never reschedule the tick to happen after we don't need to update things anymore.
<script type="text/javascript">
var counter = 0;
function counter_tick() {
if(counter < 60) {
counter++;
document.getElementById("counter").innerHTML = counter;
setTimeout(counter_tick, 1000); // Schedule next tick.
}
}
function counter_start() {
counter_tick(); // First update, also schedules next tick to happen.
}
</script>
It sounds like you are looking for a way to pause the current thread, which isn't possible in JavaScript and would probably be a bad idea anyway (the user's browser would lock up while the thread was paused).
A timer is really the way to go with this, otherwise you are fighting the way the language is intended to work.
There is no sleep-function in JS. But you can use window.setTimeout to call a function in given intervals:
function counter_start(){
// get current value
var value = document.getElementById("counter").innerHTML*1;
// leave function if 60 is reached
if(value == 60) {
return;
}
// set the innerHTML to the last value + 1
document.getElementById("counter").innerHTML=value+1;
// call next iteration
window.setTimeout(function(){counter_start()}, 100);
}
counter_start();
JSFiddle-Demo
For-loops run to completion, so you wouldn't usually use one for this.
You just need a timer and a variable to increment:
var maketimer = function(){
var tick = 0,
interval_ms = 1000,
limit = 10,
id;
return {
start: function(){
var timer = this;
console.log('start');
id = setInterval(function(){
if(tick === limit){
timer.stop();
timer.reset();
return;
}
tick += 1;
console.log(tick);
}, interval_ms);
},
stop: function(){
console.log('stop');
clearInterval(id);
},
reset: function(){
console.log('reset');
tick = 0;
}
};
};
var t = maketimer();
t.start();
If you really need to use a for-loop, then you could use a generator function. They're part of the proposed ES6 spec., and you'll need Firefox 26+ to try this out. However the only point of doing this would be to learn about generator functions.
var maketimer = function(){
var interval_ms = 1000,
limit = 10,
id,
loop,
it;
loop = function*(){
var i;
for(i=1; i<=limit; i+=1){
yield i;
}
};
it = loop();
return {
start: function(){
var timer = this;
console.log('start');
id = setInterval(function(){
var tick = it.next();
console.log(tick.value);
if(tick.done){
timer.stop();
timer.reset();
return;
}
}, interval_ms);
},
stop: function(){
console.log('stop');
clearInterval(id);
},
reset: function(){
console.log('reset');
it = loop();
}
};
};
var t = maketimer();
t.start();
Try this::
var x=0;
var myVar;
function myTimer() {
x++;
document.getElementById("counter").innerHTML = x;
if(x==60)
clearInterval(myVar);
}
function counter_start(){
myVar=setInterval(function(){myTimer()},1000);
}

setInterval - How to preserve passed in variable

I did some digging around on SO and could not find exactly what I am trying to achieve.
In simplistic terms I have a function like
function(){
for(i=0;i<10;i++){
setInterval(function(){ alert(i);), 1000)
}
}
What I would expect is 10 setIntervals that would alert 1 to 10 every 1 second, what happens is it would alert 10 always since 'i' is 10 at the end of for loop. How do I pass 'i' to setInterval anonymous function so that I can preserve the value of i in setInterval?
Above was a simplistic version of my actual problem. I am actually trying to do this
var timers = [];
function(obj){
//Clear any intervals
for(i=0;i<timer.length;i++){
clearInterval(timers[i]);
}
// Empty timers Array
timers = [];
for(i in obj){
//My object from the dom. This guy is what I am trying to preserve
my_obj = document.getElementById(i);
if(obj[i] === "Something"){
timers.push(setInterval(function(){
my_obj.replace_class(["Something", "Otherthing"],"Something");
}, 1000)
}
}
}
my_obj in the above code always refers to id = last 'i' in obj.
Do I make sense?
This should do the trick ;)
for(i = 1; i < 11; i++){
(function(local_i){
setInterval(function(){ console.log(local_i); }, 1000 * local_i)
})(i);
}
You must capture the variable in a closure. In your case this is
function capture(x) {
setInterval(function () {
console.log(x);
}, 1000);
}
for (var i = 0; i < 10; i++) {
capture(i);
}
or
function capture(my_obj) {
var id = setInterval(function() {
my_obj.replace_class(["Something", "Otherthing"],"Something");
}, 1000);
return id;
}
for (i in obj) {
//My object from the dom. This guy is what I am trying to preserve
my_obj = document.getElementById(i);
if (obj[i] === "Something") {
timers.push(capture(my_obj));
}
}

Javascript increment not working

Well I did not know what exactly would be a good title for this because it is a most peculiar situation or I'm abnormally dumb.
Here's what im trying to do.
Create a simple <meter> tag which is new in HTML5. The main issue is with my javascript. Im trying to increment the value of the meter tag gradually in my javascript. But somehow it doesn't work the way i want.
JavaScript.
for (var i = 0; i <= 10; i++) {
var a = document.getElementById("mtr1");
setTimeout(function () {
console.log(i);
a.value = i;
}, 250);
}
I'm trying to increase the value of the meter gradually every 250 ms.This doesn't happen. Instead the meter jumps straight to 10.
What interested me was the value of i that i got in the console. I got instances of 10, instead of 1,2,3...10.
Why does this happen?
FIDDLE
It's a JavaScript closures' classic. Here i is an actual reference to the variable, not its copy. After you've iterated through the loop it has the value of 10, that's why all log invocations write 10 to log.
This should work better:
for (var i = 0; i <= 10; i++) {
var a = document.getElementById("mtr1");
setTimeout(function (i) {
return function() {
console.log(i);
a.value = i;
};
}(i), 250 * i);
}
Here the most inner i is the setTimeout's callback argument, not the variable which you've declared in the loop body.
You should read more about closures in JavaScript. When a variable gets closed over, it's the same exact variable, not a copy. Since setTimeout is asynchronous, the whole loop finishes before any of the functions run, therefore the i variable will be 10 everywhere.
DEMO
function incMtrAsync(max, delay, el) {
if (el.value++ < max) {
setTimeout(incMtrAsync.bind(null, max, delay, el), delay);
}
}
incMtrAsync(10, 250, document.getElementById("mtr1"));
The above implementation implements the loop using a recursive approach. Everytime inMtrAsync is called, it checks if the value of the meter reached the max value, and if not, registers another timeout with a callback to itself.
If you want to delay the initial increment as well, just wrap the first call in another timeout.
setTimeout(incMtrAsync.bind(null, 10, 250, document.getElementById("mtr1")), 250);
Nobody used setInterval, so here's my solution ( http://jsfiddle.net/Qh6gb/4/) :
var a = document.getElementById("mtr1");
var i = 0;
var interval = setInterval(function () {
console.log(i);
a.value = ++i;
if (i == 10) {
clearInterval(interval);
}
}, 250);
The problem you describe happens before the asyncronous call to setTimeout in your original version sees a value of 10 for i because that is its value at the moment the callback is executed.
So, this is a problem with the scope of the closure, to make it work you should make it like this:
for (var i = 0; i <= 10; i++) {
var a = document.getElementById("mtr1");
(function (i, a) {
setTimeout(function () {
console.log(i);
a.value = i;
}, 250);
})(i, a);
}
also, since a is always the same, this should be better:
var a = document.getElementById("mtr1");
for (var i = 0; i <= 10; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
a.value = i;
}, 250);
})(i);
}
If then you want to see the counter "ticking up", this will make it visible:
var a = document.getElementById("mtr1");
for (var i = 0; i <= 10; i++) {
(function (i) {
setTimeout(function () {
console.log(i);
a.value = i;
}, 1000 * i);
})(i);
}
See http://jsfiddle.net/LDt4d/
It happens because you called setTimeout, which is "asynchronous". So setTimeout is called 10times but after whole loop is done then it is executed. Therefore, i = 10 in each call...
http://jsfiddle.net/Qh6gb/9/
there is the solution:
var i = 1,
meter = document.getElementById("mtr1");
function increase() {
meter.value = i++;
console.log(i);
if(i<=10) {
setTimeout(increase, 250);
}
}
setTimeout(increase, 250);
you can use timeout jquery plugin:. It is easier
However you should calculate your timeout ,
For you ,timeout=250*max=250*10=2500
So
$('meter').timeout(2500);
Demo
Run for loop inside the function instead of declaring a closure in every step of the loop.
JSFIDDLE: http://jsfiddle.net/Qh6gb/3/
var a = document.getElementById("mtr1");
setTimeout(function () {
for (var i = 0; i < 10; i++) {
console.log(i);
a.value = i;
}
}, 250);
I hope I understand right. Please try and tell me if you got solution.
var count = 0;
function increment(){
document.getElementById("meter").value = count;
count++;
if(count ==10)
count=0;
}
setInterval(increment, 250);
Please check with jsFiddle
You're creating multiple functions that are all being set off at the same time.
Multiply the timer by i for correct delay.
for (var i = 0; i <= 10; i++) {
var a = document.getElementById("mtr1");
setTimeout(function () {
console.log(i);
a.value = i;
}, 250 * i);
}

Closure for setInterval function in javascript

How to use setInterval without using global variables? I'd prefer to wrap all variables of function invoked by setInerval in some kind of closure, like so:
var wrap = function (f){
var local1, local2, ...;
return function () { return f(); }
}
This doesn't work, but the idea is that I'd pass wrap(f) instead of f to setInterval, so that locals for f are nicely wrapped and don't pollute the global scope.
javascript don't have dynamic binding.(except this keyword)
use anonymous function can archive your idea. (it called closure)
var fnc = function(){
var local1, local2;
return function(){
// using local1, local2
}
};
setInterval(fnc, 1000);
I assume you're looking for something like this...
var wrap = function (f){
var locals = Array.prototype.slice.call(arguments, 1);
return function () { f.apply(this, locals); }
};
function logger_func() {
console.log.apply(console, arguments);
}
for (var i = 0; i < 10; i++) {
setTimeout( wrap(logger_func, i, "foo_" + i), // <-- wrapping i
i * 1000 );
}
Note that modern environments let you pass extra arguments to setTimeout...
for (var i = 0; i < 10; i++) {
setTimeout(logger_func, i * 1000, i, "foo_" + i);
}

Categories