JavaScript async callback and scope - javascript

Consider the following example:
var cb = function (t) {
console.log('callback -->' + t);
};
for(var i = 0; i<3; i++) {
console.log(i);
setTimeout(function(){
cb(i);
},1000);
}
Working example at jsfiddle
The output of this code snippet is:
0
1
2
callback ---> 3
callback ---> 3
callback ---> 3
Everything works as expected, for loop puts 3 callback calls into the event loop. By the end of the for loop i == 3 and when the callbacks get executed all of them print 3 because they contain the link to the i which is 3.
How could this snippet be improved so when the callback gets executed it uses the actual value which was passed to it.
The output should be:
callback ---> 1
callback ---> 2
callback ---> 3
Thanks in advance.

Create a closure so that the setTimeout handler will refer to the closure's local variable (which in this case, we also named i), not the i from the loop:
for (var i = 0; i < 3; i++) {
(function (i) {
console.log(i);
setTimeout(function () {
cb(i);
}, 1000);
}(i));
}

You could try .bind:
for(var i = 0; i<3; i++) {
console.log(i);
setTimeout(cb.bind(null, i),1000);
}
The demo.
The traditional way to handle this is to create a closure:
for(var i = 0; i<3; i++) {
console.log(i);
setTimeout((function(i){return function(){cb(i)}}(i)),1000);
}

A frequent question.
Let's try to use some features of future of JS. I mean let.
It creates local scope variable and you don't need to use a closure or another trick.
But now it works only in FF (i'm using 20.0.1)
for(var i = 0; i<3; i++) {
console.log(i);
let a = i;
setTimeout(function(){
cb(a);
},1000);
}

I will just use setTimeout() in your cb's functoin
var cb = function (t) {
setTimeout(function(){
console.log('callback -->' + t);
},1000);
};
for(var i = 0; i<3; i++) {
console.log(i);
cb(i);
}

Related

why is IIFE needed to create a new scope?

From You Don't Know JS:
for (var i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
gives
6
6
6
6
6
but using an IIFE like so
for (var i=1; i<=5; i++) {
(function(){
var j = i;
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})();
}
gives
1
2
3
4
5
My question: why doesn't
for (var i=1; i<=5; i++) {
setTimeout( function timer(){
var j = i;
console.log( j );
}, i*1000 );
}
or
for (var i=1; i<=5; i++) {
function timer() {
var j = i;
console.log(j);
}
setTimeout(timer, i*1000 );
}
work like the IIFE example? It seems to me they both have a function declaration with a new variable j, wouldn't that create a new lexical scope with a specific setting for i?
The important part of the IIFE is that it runs right away; before i changes, it reads its value and puts it in a new variable. The function reading i in your other examples – function timer() – does not run right away, and the value it puts in its new variable is the value of i after it’s already changed.
Also, in ES6, you can just let i = … instead of var i = … and it’ll work fine without the IIFE or j:
for (let i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
because let has block scope instead of function scope and variables declared in the initialization part of for loops count as being half-inside the for’s block.
i, being declared with var, is hoisted. Variables don't automatically get their scopes bound to an inner function; unless the inner function explicitly has var i or a paramter of i (thus defining a new i bound to the scope of the inner function), i will continue to refer to the hoisted i in the outer scope.
For example, you could do what you were thinking of like this, if you wanted:
for (var i=1; i<=5; i++) {
setTimeout( function timer(i){
console.log( i );
}, i*1000, i );
}
(The third argument to setTimeout is what the function, the second argument, will be called with)
This means that timer will be called with i as it is during iteration, and the function will use a new i, bound to the scope of the function, initialized via the parameter.
It's a pretty bad idea, though - better to use const and let, which have block scope rather than function scope, and better not to shadow outer variables.
This sort of IIFE
for (var i=1; i<=5; i++) {
(function(){
var j = i;
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})();
}
is often written like
for (var i=1; i<=5; i++) {
(function(j){
setTimeout( function timer(){
console.log( j );
}, j*1000 );
})(i);
}
so, you can see that "captured" value is i in this case
You can do the same without IIFE
for (var i=1; i<=5; i++) {
function timer(j) {
setTimeout(function() {
console.log(j);
}, j * 1000 );
}
timer(i);
}
of course, this is equivalent of
function timer(j) {
setTimeout(function() {
console.log(j);
}, j * 1000 );
}
for (var i=1; i<=5; i++) {
timer(i);
}
if using ES2015+, you can use let
for (let i=1; i<=5; i++) {
setTimeout( function timer(){
console.log( i );
}, i*1000 );
}
Now, if you use a transpiler because you need to support ES5 (or whatever internet exploder supports) you'll see that the transpiled version is
var _loop = function _loop(i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
};
for (var i = 1; i <= 5; i++) {
_loop(i);
}
Which looks incredibly like the previous version of the code

Passing parameters to the function in setTimeout

I want to call a function in a cycle with setTimeout(), and also pass the counter as a parameter. Here's what I tried:
<!DOCTYPE html>
<html><body>
<button onclick="timed1()">Display alerts</button>
<script>
function timed1() {
for (i=1; i<4; i++){
setTimeout(
(function(i){ alert(i) })(i), 1000*i
);
}
}
</script>
</body></html>
Function calls are done with correct values, but immediately instead of the intervals I wanted. I have read this and this SO questions but unfortunately don't see yet what should I change in my case.
You can use let here if you want to bind different values of i to the setTimeout function
function timed1() {
for (let i=1; i<4; i++){
setTimeout(function() {
alert(i)
}, 1000*i)
}
}
timed1()
You're swimming on IIFE (Immediately Invoked Function Expression).
You're calling your function immediately.
setTimeout(
(function(i){ alert(i) })(i), 1000*i
); ^
The scope of your variable i is open, you need to use let keyword.
for (i = 1; i < 4; i++) {
^
Look at this code snippet
function timed1() {
for (let i = 1; i < 4; i++) {
setTimeout(
function() {
alert(i)
}, 1000 * i);
}
}
<button onclick="timed1()">Display alerts</button>
The setTimeout function allows you to pass arguments to the callback function, like so:
function timed1() {
for (i=1; i<4; i++){
setTimeout(alert, 1000 * i, i);
}
}
timed1()
Any arguments after the time are passed along to the callback.
The expression (function(i){ alert(i) })(i) does not return a function for setTimeout to call. If you're going the IIFE way, make sure to return a new function from it:
setTimeout((function(i) {
return function() {
alert(i);
};
})(i), 1000 * i);
With ES2015+ a far better readable equivalent can be realized:
for (let i = 1; i < 4; i++) {
setTimeout(() => alert(i), 1000 * i);
}

Value gets overwritten because of access by reference

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.

Delay in for loop

I´m having trouble to delay a function in a for-loop.
So here is my test:
<script type="text/javascript">
var j = 0;
for(var i = 0; i <= 100; i++){
setTimeout(function(){
console.log(i);
},j*2);
j++;
}
</script>
I want that every count will appear step by step with a delay in the console. But currently only 101 appears 101 times. Why and what is a better solution?
Cheers
JavaScript doesn't have block level scoping of variables, so the value of i in any deferred functions will be the last value of i from the loop (in your case, 101). You can either use a named function or an immediately invoked function expression to create a closure that gives the value the correct scope.
<script type="text/javascript">
var j = 0;
for(var i = 0; i <= 100; i++){
(function(i) {
setTimeout(function(){
console.log(i);
},j*2);})(i);
j++;
}
</script>
Also bear in mind that the second argument passed to setTimeout is the delay in milliseconds, so that's a very short delay (a tiny fraction of a second) between each call.
function SetTimeoutLoop(i) {
setTimeout(function() { console.log(i); }, i*2);
}
for(var i = 0; i <= 100; i++){
SetTimeoutLoop(i)
}
JSFIDDLE
u may try this:
<script type="text/javascript">
var j = 0;
for(var i = 0; i <= 100; i++){
(function(i, j){
setTimeout(function(){
console.log(i);
},j*2);
})(i, j);
j++;
}
</script>
It happens as once your callback starts to fire your loop would be finished and the value will be max.
you can do like
(function me(i,j) {
if(i <= 10) {
setTimeout(function(){
alert(i++);
me(i,j);
},j++*2);
}
})(0,0);
Here is a sample fiddle.
You can't do that in for loop, instead you should use a recursive function like this:
here's a fiddle
var j = 0;
var i=0;
setTimeout(function(){
log_i(i);
},j*2);
function log_i(i){
console.log(i);
i++;
if(i<=100){
setTimeout(function(){ log_i(i); },j*2);
}
j++;
}

Can't call a function through setTimeout

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);
}

Categories