Unexpected behavior with closures: callback holds the last value - javascript

I'll get to the point: I have this loop:
for (var i = 1; i <= toSchedule; i++) {
when = trackWrapper.lastPlay +
(trackDuration +
(looper.timeInterval - trackDuration));
track.play(true, when);
trackWrapper.lastPlay = when;
}
The play method has this inside the body:
[...]
// Here when is a different value for each call (verified)
// Many calls of the play method are performed before the returned function below is run as a callback
function playingCallback(myWhen){
return function(buffers){
// Here myWhen will always be equal to the value of the last call occurred BEFORE the first callback execution
console.log("myWhen = "+myWhen);
[...]
};
};
var realCallback = playingCallback(when);
track.scheduled.push(when);
track.recorder.getBuffer(realCallback);
So, for example:
play(true, 1);
play(true, 2);
play(true, 3);
// Wait for it...
myWhen = 3;
myWhen = 3;
myWhen = 3;
Now: I've read about closures, I've read about the "infamous loop problem", I've read tens of answers here on StackOverflow but I couldn't figure this out. It's the second time I have this kind of problem with callbacks so, at this point, I guess I haven't completely understood what is going on.
Could you please explain to me what is supposed to be wrong with the code above? Thank you in advance.

Generally you should understand the following rule: A clousure will have access to its "surrounding scope", even after the scope has been exited. But it will be the state of the scope at execution time and not(!) at creation time of the closure
If you create a closure inside a loop, it will have access to the loop variable. But the loop will most likely already have ended. So the loop variable will hold the value of its last loop.
So if your closure is a callback you should create a copy of your relevant scope variable(s) at creation time and use this copy at execution time. You can do this (for example) by creating an inner closure from an immediately executing anonymous function
function myOuterScope(count) {
for(i=0; i<count; i++) {
setTimeout((function(local_i) {
// this function will be immediately executed. local_i is a copy of i at creation time
return function() {
// this is the function that will be called as a callback to setTimeout
// use local_i here, and it will be 0, 1, 2 instead of 3, 3, 3
}
})(i)
, 1000);
}
}
myOuterScope(3);

Related

Variable is lost when passed as a parameter in setTimeout() [duplicate]

This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I have a problem with calling a function with a parameter inside a setTimeout function. Basically I'm trying to make a small online game, where I create a queue of commands and then execute them one at a time (each takes some time to show a visualization).
Unfortunately it seems that I cannot pass any variable as a parameter inside the setTimeout(). Although the variable does exist when I call the function it does not exist later when it is executed. The function doesn't keep track of the passed value.
Is there any solution to this? Thanks a lot for any help. Here is a code I use:
function executeCommands() {
var commands = document.getElementsByClassName("cmdplace");
var timeout = 0;
for (i = 0; i < commands.length; i++) {
console.log(commands[i].childNodes[0]); //variable exists
setTimeout(function() {go(commands[i].childNodes[0]);}, timeout+=400); //Uncaught TypeError: Cannot read property 'childNodes' of undefined
console.log(commands[i].childNodes[0]); //variable still exists
}
}
function go(command) {
//do somethig based on the passed command
}
When your functions are invoked, i is equal to commands.length and commands[i] is undefined.
They are capturing the variable i, not its value.
When they execute, they get out of i the actual value, but so far it has reached commands.length (that is the condition used to break your loop).
You can do something like this to work around it:
setTimeout(function(j) {
go(commands[j].childNodes[0]);
}.bind(null, i), timeout+=400);
Or this:
setTimeout((function(j) {
return function() {
go(commands[j].childNodes[0]);
};
})(i), timeout+=400);
Note also that, as you defined it, i is a global variable.
As mentioned in the comments by #PMV, there's a much easier way in modern JavaScript (if that's an option for you).
Just use a let statement as it follows:
for (let i = 0; i < commands.length; i++) {
// do whatever you want here with i
}
This will ensure that each iteration gets a new variable named i and you can capture it as in the original code.
You need to make a distinct copy of each item. By the time the setTimeout runs the loop has already finished.
var timeout = 0;
function executeCommands() {
var commands = document.getElementsByClassName("cmdplace");
for (i = 0; i < commands.length; i++) {
go(commands[i]);
}
}
function go(command) {
setTimeout(function() {
console.log(command);
}, timeout += 400);
}
executeCommands();
<ul>
<li class="cmdplace">A</li>
<li class="cmdplace">B</li>
<li class="cmdplace">C</li>
<li class="cmdplace">D</li>
</ul>

how do functions work in javascript

i have a simple function called Range that creates an array of integers based on start, step and end value...
function Range (start, end, step) {
// default step is 1..
if (step === undefined ) step = 1;
// creating an array...
var arr = [], index = 0;
while(start <= end) {
arr[index] = start ;
index += 1;
start += step;
}
// simple function expressions
var getAll = function () {
return arr ;
};
var getOne = function(n) {
return arr[n] ;
};
// returns a unnamed function ..
return function(i) {
if (i === undefined) { return getAll() ;}
else {return getOne(i); }
}; // not an iife
}
so basically Range is a function which returns a unnamed function which again returns a named function expression declared in the function Range.. err.. i dont know.. something like that...
now the below code...
var first10 = Range (1,10) ; // no new ..() here, so no instance should be created.. only Range is called..
var first10Odd = Range(1,20,2) ; // and Range is called again..
alert(first10); // alerts - function(i) { ... }
alert(first10Odd); // alerts- function(i) { ... }
alert(first10()) ; // alerts - 1,2,3,...10
alert(first10Odd()); // alerts - 1,3,5,...19
alert(first10(0)); // alerts - 1
alert(first10Odd(9)); // alerts- 19
why do the alerts alert as specified in the comments??... i think Range is a just a function and not a object constructor and also no instance was created... shouldn't the local variables of function be destroyed as soon as the function is completed??
or is my logic wrong?? what is going on in the above code?? can anyone please explain....
i have made a fiddle of my code here..
sorry for asking this stupid question..
Welcome to the land of closures in Javascript. They can be very powerful and extremely useful once you understand them. But, if your prior experience is with languages that do not have them, they can feel a bit foreign at first.
Some answers/explanation:
Calling Range(x, y) returns a function that can then be called later.
Because that function that is returned is inside another function scope that has variables, a closure is created.
That closure stays alive (even though the outer function has finished executing) because there is a lasting reference to the inner function saved in your variables and that inner function has a reference to the local variables in the outer function. These references keep the closure from being garbage collected (so it stays alive).
That inner function can then reference the variables in the outer function, including the arguments originally passed to it.
This construct allows you to create these custom functions that have arguments pre-built into them.
The notion of this type of closure only exists in some languages. It does not exist in C++, for example.
When the function returned by calling Range(x,y) is itself executed later, it can use any of the variables that were originally in scope to it.
Each call to Range(x,y) causes a new closure to be created.
getAll and getOne are local variables in the outer function that are assigned a function. They access other local variables in the outer function. All of these are in the previously mentioned closure that is created each time Range() is called.
There is lots written about what a closure is (which you can Google and read), but I like to think of it as an execution context that contains everything that was in scope at the time a function is called (including all variables). Each time a function is called, such an execution context it created. Since everything in javascript is garbage collected and will only be freed/destroyed when there are no references left to it, this is true for this execution context too (e.g. closure). As long as something has a reference to it or something in it, then the execution context will stay alive and can be used by any code that might run into that execution context.
Line by line annotation:
// first10 is assigned the anonymous function that the call to Range()
// returned. That anonymous function has access to the original arguments
// passed to the Range(1,10) call and other local variables in that function.
var first10 = Range (1,10) ; // no new ..() here, so no instance should be created.. only Range is called..
// same as the call before, except this also includes the step argument
var first10Odd = Range(1,20,2) ; // and Range is called again..
// this makes sense because Range(1,10) returns a function so
// when you alert it's value, it tells you it's a function
alert(first10); // alerts - function(i) { ... }
alert(first10Odd); // alerts- function(i) { ... }
// When you execute the function in first10, it runs that function
// and the alert shows the return value from that function
// This particular function is set to return the entire array if nothing is passed
// to it
alert(first10()) ; // alerts - 1,2,3,...10
alert(first10Odd()); // alerts - 1,3,5,...19
// This particular function is set to return a specific index from the array
// if an argument is passed to it
alert(first10(0)); // alerts - 1
alert(first10Odd(9)); // alerts- 19
If you know how to use the javascript debugger, you can set a breakpoint on this line if (i === undefined) { return getAll() ;} in the inner function and you will be able to inspect all the variables that are in scope, including start, end and step from the outer function.
You may find this article useful reading as it encapsulates some of the ways that closures can be used with object declarations: http://javascript.crockford.com/private.html (not exactly what is being done here, but might help you understand them).
Welcome to javascript closures. Lets take line by line.
var first10 = Range(1,10);
var first10Odd = Range(1,20,2);
We know that Range is just a function. So, in these two lines we are just calling Range function with 2 and 3 arguments respectively.
Now, what happens when you call a function. The obvious answer is, the body of the function gets executed. What do we have in the body of the function.
if (step === undefined ) step = 1;
var arr = [], index = 0;
while(start <= end) {
arr[index] = start ;
index += 1;
start += step;
}
I hope that the above seen lines are pretty obvious and you don't have any problems with them.
var getAll = function () {
return arr;
};
What does this line do? It creates a function at run time. Why runtime? Lets see an example.
<script>
func1();
var func1 = function() {
alert("Hi");
}
</script>
<script>
func1();
function func1() {
alert("Hi");
}
</script>
If you use the first script block, it will throw error. Why? You are calling a function which hasn't been defined yet. The second case, you are defining the function during javascript parsing time itself. The type of function which was created in the first case is called anonymous function. Let us get back to getAll. Now we know that getAll is simply a variable which points to an anonymous function, lets look at what it does. It returns arr. How does it have access to arr? It is declared outside the function and so it still has access to it. Same case with
var getOne = function(n) {
return arr[n] ;
};
Now the very important part,
return function(i) {
if (i === undefined) {
return getAll();
} else {
return getOne(i);
}
};
What does it do? It returns a function. To be precise, it returns an anonymous function. Whenever Range is called, it creates a new anonymous function, which accepts one parameter and returns it. So, now what do first10 and first10Odd have? Yes. You are right, they have functions. I hope that explains
alert(first10); // alerts - function(i) { ... }
alert(first10Odd); // alerts - function(i) { ... }
Let us examine both the functions. When first10 is called with nothing, I mean, first10(), the parameter i takes the value undefined. So, we are actually making a call to the anonymous function with no parameters and it is supposed to return getAll(). If you remember, first10 was created with Range(1,10);. So, the arr will now have [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
You might ask, when we return from the function, wont the variables declared inside the function go out of scope. The answer is Yes and No. Yes, when you simply return a value. No, when you return a function. When you return a function, the state of the variables will be maintained. This property is called closures. That is why it returns
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] for alert(first10())
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19] for alert(first10Odd())
1 for alert(first10(0))
19 for alert(first10Odd(9))
Please read more about Closure here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Closures

pass counter to anonymous function

Kindof a basic question, but how do I pass a distinct value to an anonymous function?
Maybe there's a better way of phrasing this... what I mean is, consider the following code... assuming I have a bunch of clickable items with ids like somediv1, somediv2, etc.
for(idx = 1; idx < 30; idx++) {
$("#somediv" + idx).on('click', function() {foo(idx); });
}
function foo(inIDX) {
alert(inIDX);
}
No matter which div I click, it always alerts 30. I'd like clicking somediv1 to alert 1, somediv2 to alert 2, and so on
When the callback is called idx has the value of end of loop.
Here's a standard solution :
for(idx = 1; idx < 30; idx++) {
(function(i){
$("#somediv" + idx).on('click', function() {foo(i); });
})(idx);
}
The intermediate function, which is called immediately, stores the value of idx at time of execution of the loop.
Maybe I could make it clearer by remembering that in JavaScript
the scope of a variable is either a function or the global scope (never a simple block)
a function keeps a pointer to the scope in which it was declared and can use the variables of this scope when it's executed (this is called a closure)

Reassign variables stored in closure using a callback or global function

EDIT
Let me get more to the point. I'm trying to create a psuedo promise implementation. The idea here being that I have a callback that won't be executed until an asynchronous call is received. So I'm simply queueing up all the calls to this function until the time at which it's notified that it can be executed. The queue is emptied and any further call to the function is SUPPOSED to execute immediately, but for some reason, the function is still queueing. This is because, for whatever reason, my redefinition of the runner function is not working correctly. The code below was my sleep deprived, frustrated version of every thought that went through my head. Here's the actual code:
function Promise(callback){
var queue = []
, callback = callback
, runner = function(){
queue.push({
context: this,
args: Array.prototype.slice.call(arguments, 0)
});
}
;//var
runner.exec = function(){
for(var i = 0, ilen = queue.length; i < ilen; i++){
var q = queue[i];
callback.apply(q.context, q.args);
}
runner = callback;
};
return runner;
}
test = Promise(function(){
$('<div/>').appendTo('#output').html(Array.prototype.slice.call(arguments,0).toString());
});
test(1,2);
test(3,4);
test.exec();
test(5,6);​
http://jsfiddle.net/a7gaR/
I'm banging my head against the wall with this one. I'm trying to reassign variables in a function from a call outside the function itself (ideally by passing a reassignment function as a callback). In the example I posted on jsfiddle, I made a global function that, in theory, has a reference to the variables contained within its parent function. Upon calling that external function, I expect it to reassign the values that the other function is using. It doesn't seem to work this way.
window.test = function temp() {
var val = 7,
func = function() {
return val;
};
window.change = function() {
window.test.val = 555555;
$('<div>Changing ' + val + ' to ' + window.test.val +
'</div>').appendTo($output);
val = window.test.val;
temp.val = window.test.val;
func = function() {
return 'Why isn\'t this working?';
}
}
return func();
}
var $output = $('#output');
$('<div/>').appendTo($output).html('::' + test() + '::');
window.change();
$('<div/>').appendTo($output).html('::' + test() + '::');
http://jsfiddle.net/YhyMK/
The second time you call test you're creating a new local variable called func and defining a new window.change that closes over that new variable. The changes you made to the original func by calling the original window.change are not relevant in the second call.
Also note that the following line:
window.test.val = 555555;
...does not modify/refer to the val variable in the outer function. window.test.val refers to a property named val on the test object (which happens to be a function), not any local variable.
You are trying to refer to a local variable in a function with the syntax func.varname. That won't work, that's not the way local variables work.
I finally created a function that would perform this operation. The gist for it is here: https://gist.github.com/2586972.
It works like this...
You make a call to Defer passing the callback whose functionality you'd like to delay:
var deferredCB = Defer(function(){ console.log(this,arguments) };
deferredCB will now store all of the arguments you pass allowing them to be executed at some later date:
defferedCB(1);
defferedCB(2);
Now, when you're ready to perform the operation, you simply "execute" deferredCB:
defferedCB.exec();
Which results in:
// window, 1
// window, 2
All future calls to deferredCB will be executed immediately. And now that I'm thinking about it, I'll probably do a rewrite to allow you to reset deferredCB to it's pre-executed state (storing all the arguments again).
The key to making it work was having a wrapper function. Javascript simply won't let you reassign a function while it's being executed.
TADA!!!

How do I execute a function after the callbacks inside a for loop are completed?

I have a for loop in a search function, with a function that does a callback inside the loop, and I want to execute a BUILD() function after the loop, and after all the callbacks are completed. I am not sure how to do that, because the loop finishes before all the callbacks are done. The callbacks are API requests to get me data, and I want to BUILD() with that data.
I read up on deferred, so I tried to put the for loop inside a function to the deferred, and then calling BUILD() on '.then( ... )'. But that doesn't seem to work - I think I am understanding it wrong.
HELP?!
Note, this is using the Google Maps Places API (search and getDetails).
var types = {
'gym' : 'fitness, gym',
'grocery_or_supermarket': ''
}
function search() {
for (var key in types) {
var request = { ... };
service.search(request, searchCallback);
}
// PROBLEM AREA
BUILD();
}
function searchCallback(results, status) {
for (var i = 0; i < results.length; i++) {
var request = { ... };
service.getDetails(request, detailsCallback);
}
}
function detailsCallback(place, status) {
// add place marker to maps and assign info window and info window event
}
With a small modification of your code, it can be achieved.
var total = 1337; // Some number
var internal_counter = 0;
var fn_callback = function() {
searchCallback.apply(this, arguments);
if (++internal_counter === total) {
BUILD();
}
};
for (var i=0; i<total; i++) {
service.search(request, fn_callback);
...
Explanation
First, we create a local function and variable.
The variable is a counter, which is increased when the callback is called.
The function is passed to the asynchronous method (service.search), which calls the original callback. After increasing the counter, check the value of the counter against the variable which holds the total number of iterations. If these are equal, call the finishing function (BUILD).
A complex case: Dealing with nested callbacks.
var types = { '...' : ' ... ' };
function search() {
var keys = Object.keys(types);
var total = keys.length;
// This counter keeps track of the number of completely finished callbacks
// (search_callback has run AND all of its details_callbacks has run)
var internal_counter = 0;
for (var i=0; i<total; i++) {
var request = { '...' : ' ... ' };
services.search(request, fn_searchCallback);
}
// LOCAL Function declaration (which references `internal_counter`)
function fn_searchCallback(results, status) {
// Create a local counter for the callbacks
// I'm showing another way of using a counter: The opposite way
// Instead of counting the # of finished callbacks, count the number
// of *pending* processes. When this counter reaches zero, we're done.
var local_counter = results.length;
for (var i=0; i<results.length; i++) {
service.getDetails(request, fn_detailsCallback);
}
// Another LOCAL function (which references `local_counter`)
function fn_detailsCallback(result, status) {
// Run the function logic of detailsCallback (from the question)
// " ... add place marker to maps and assign info window ... "
// Reduce the counter of pending detailsCallback calls.
// If it's zero, all detailsCallbacks has run.
if (--local_counter === 0) {
// Increase the "completely finished" counter
// and check if we're finished.
if (++internal_counter === total) {
BUILD();
}
}
} // end of fn_detailsCallback
} // end of fn_searchCallback
}
The function logic is explained in the comments. I prefixed the heading of this section with "Complex", because the function makes use of nested local functions and variables. A visual explanation:
var types, BUILD;
function search
var keys, total, internal_counter, fn_searchCallback;
function fn_searchCallback
var result, status; // Declared in the formal arguments
var local_counter, i, fn_detailsCallback;
function fn_detailsCallback
var result, status; // Declared in the formal arguments
In the previous picture, each indention level means a new scope Explanaation on MDN.
When a function is called, say, 42 times, then 42 new local scopes are created, which share the same parent scope. Within a scope, declared variables are not visible to the parent scope. Though variables in the parent scope can be read and updated by variables in the "child" scope, provided that you don't declare a variable with the same name. This feature is used in my answer's function.
I think you understand this already, but as it is the BUILD() is getting called linearly while the previous callback functions are still running. It's like you've created extra threads. One way to solve the problem would be to make BUILD a callback from the search function with the for loop in it. This would guarantee all functionality is complete before calling it.
This question might help implement the callback: Create a custom callback in JavaScript

Categories