Weird index issue in Backbone click event callback - javascript

I have this code, it's independent and isolated. The problem I am having is that the index i is starting at 1 instead of starting at 0. I have no idea why this could be, and doesn't seem to have anything to do with the closure that I am pushing into the deletes array...but I can't be sure, no idea what the issue is.
onClickResetAll: function (event) {
event.preventDefault();
var deletes = [];
Object.keys(collections).forEach(function (key) {
if (collections.hasOwnProperty(key)) {
var coll = collections[key];
for (var i = 0; i < coll.models.length; i++) {
deletes.push(function (callback) {
var index = i; //i starts at 1, not 0 !!!
coll.models[index].deleteModel(function (err, resp, x) {
console.log(err, resp, x);
if(err){
callback(err);
}
else{
callback(null,null);
}
});
});
}
}
});
async.parallel(deletes,function(err,results){
Backbone.Events.trigger('bootRouter', '+refreshCurrentPage');
});
}, //end of onClickResetAll callback function
//end

The problem isn't really that i starts at one, the problem is that i will be coll.models.length for every function in deletes. Why would that be? Well, each function is sharing the same i and i won't be evaluated until the functions inside deletes are actually called.
The solution is to force i to be evaluated when it has the value you want (i.e. evaluated i when you're building the callback function). There are various solutions and they're all variations on the "wrap it in a function to break the reference" theme:
Use an iterator with a callback function instead of a plain for loop:
coll.each(function(model, i) {
// `model` is the model from the collection, `i` is the loop index.
});
You can use each here because Backbone collections have a bunch of Underscore functions built in.
Wrap the loop body in an SIF:
for(var i = 0; i < coll.models.length; ++i)
(function(i) {
//...
})(i);
Use a separate function to build your functions:
function make_deleter(coll, i) {
return function(callback) {
coll.models[i].deletedModel(function(err, resp, x) {
//...
}
}
}
//...
for(var i = 0; i < coll.models.length; ++i)
deletes.push(make_deleter(coll, i));
They all do pretty much the same thing: add an extra function call into the mix to force i to be evaluated (rather than just referenced) on each iteration of the loop.
In a Backbone situation, 1 would probably be the most natural and you wouldn't even need your troublesome i with that approach.

another solution to this is using async.each or async.eachSeries instead of async.parallel. using the first 2 avoids pushing to an array of functions altogether.

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.

JavaScript / Binding index with Node.js

I have the following code (dbclient is Redis client for Node.js):
dbclient.zrange("cache", -1000000000000000, +1000000000000000, function(err, replies) {
logger.info("Replies: " + replies.length);
logger.info(err);
for (var i = 0; i < replies.length; i++) {
logger.info("I: " + i)
dbclient.hmget("doc:" + replies[i], "size", function(err, res) {
cache.set(replies[i], parseInt(res[0]));
logger.info(res[0]);
});
}
});
I notice a strange behavior:
The first output is: Replies: 195748, but in the for loop I notice that it always prints I: 195747 and the res[0] is always 198536. This run 195747 times.
It seems that it's stuck on the last index, and doesn't iterate over all items.
This is one of the most common errors in Javascript, in
function(err,res){
cache.set(replies[i], parseInt(res[0]));
logger.info(res[0])
}
you are using i, but that is always the one from the parent scope. Since the function is run as a callback asynchronously, it is always the value of the last loop iteration.
Change it to
(function(i) {
return function(err,res){
cache.set(replies[i], parseInt(res[0]));
logger.info(res[0])
};
})(i)
to bind i to the inner-most function's scope
further explanation: JavaScript closure inside loops – simple practical example

JavaScript scope, break for cycle from function callback

I need to break cycle if proccess = true, but it's undefined.
var mapFound;
var locations = {$addressTest};
for (var i = 0; i < locations.length; ++i) {
getCoordinates(
locations[i],
function(proccess) {
}
)
if(proccess) { break; }
}
The problem seems to be basically that getCoordinates() makes an asynchronous call.
By the time the loop is over you haven't received even the first response, based on your question text, so you need to use another solution.
I mean by this that you won't be able to break the cycle, because by the time the cycle is over you still don't know if process is true.
Depending on your implementation you might want to take a look at promises. Although it might be easier to wrap the whole thing in a function that executes a callback:
function getCoords(locations, name, callback){
for (var i = 0; i < locations.length; i++) {
getCoordinates( locations[i], name,
function(proccess) {
if(process){
console.log("Map found in "+locations[i]);
callback.call();
}
}
);
}
}
getCoords({$addressTest}, {$name}, function(){
// Place here whatever you want to do once the map is found.
// you can get the map location by passing it as an argument for the callback.
});

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?

Understanding closures: Constructing a meta-function that queues functions together

In terms of solving the problem, I have a fully working solution that I just finished here:
// synchronous dynamic script loading.
// takes an array of js url's to be loaded in that specific order.
// assembles an array of functions that are referenced more directly rather than
// using only nested closures. I couldn't get it going with the closures and gave up on it.
function js_load(resources, cb_done) {
var cb_list = []; // this is not space optimal but nobody gives a damn
array_each(resources, function(r, i) {
cb_list[i] = function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() {
console.log("js_load: loaded "+r);
if (i === resources.length-1) {
cb_done();
} else {
cb_list[i+1]();
}
};
};
});
cb_list[0]();
}
I am completely happy with this because it does what I want now, and is probably far easier to debug than what my first approach, if it had succeeded, would have been.
But what i can't get over is why I could never get it to work.
It looked something like this.
function js_load(resources, cb_done) {
var cur_cont = cb_done;
// So this is an iterative approach that makes a nested "function stack" where
// the inner functions are hidden inside the closures.
array_each_reverse(resources, function(r) {
// the stack of callbacks must be assembled in reverse order
var tmp_f = function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() { console.log("js_load: loaded "+r); cur_cont(); }; // TODO: get rid of this function creation once we know it works right
};
cur_cont = tmp_f; // Trying here to not make the function recursive. We're generating a closure with it inside. Doesn't seem to have worked :(
});
cur_cont();
}
It kept trying to call itself in an infinite loop, among other strange things, and it's really hard to identify which function a function is and what a function contains within it, during debugging.
I did not dig into the code, but it appears that jQuery.queue has also implemented a similar mechanism to my working one (using an array to track the queue of continuations) rather than using only closures.
My question is this: Is it possible to build a Javascript function that can take a function as argument, and enhance it with a list of other functions, by building closures that wrap functions it creates itself?
This is really hard to describe. But I'm sure somebody has a proper theory-backed mathematical term for it.
P.S. Referenced by the code above are these routines
// iterates through array (which as you know is a hash), via a for loop over integers
// f receives args (value, index)
function array_each(arr, f) {
var l = arr.length; // will die if you modify the array in the loop function. BEWARE
for (var i=0; i<l; ++i) {
f(arr[i], i);
}
}
function array_each_reverse(arr, f) {
var l = arr.length; // will die if you modify the array in the loop function. BEWARE
for (var i=l-1; i>=0; --i) {
f(arr[i], i);
}
}
The problem is how you were setting the value of cur_cont for every new function you made, and calling cur_cont in the onload callback. When you make a closure like tmp_f, any free variables like cur_cont are not 'frozen' to their current values. If cur_cont is changed at all, any reference to it from within tmp_f will refer to the new, updated value. As you are constantly changing cur_cont to be the new tmp_f function you have just made, the reference to the other functions are lost. Then, when cur_cont is executed and finishes, cur_cont is called again. This is exactly the same function that had just finished executing - hence the infinite loop!
In this sort of situation, where you need to keep the value of a free variable inside a closure, the easiest thing to do is to make a new function and call that with the value you want to keep. By calling this new function, a new variable is created just for that run, which will keep the value you need.
function js_load(resources, cb_done) {
var cur_cont = cb_done;
array_each_reverse(resources, function(r) {
// the stack of callbacks must be assembled in reverse order
// Make a new function, and pass the current value of the `cur_cont`
// variable to it, so we have the correct value in later executions.
// Within this function, use `done` instead of `cur_cont`;
cur_cont = (function(done) {
// Make a new function that calls `done` when it is finished, and return it.
// This function will become the new `cur_cont`.
return function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() {
console.log("js_load: loaded "+r);
done();
};
};
})(cur_cont);
});
// Start executing the function chain
cur_cont();
}
EDIT: Actually, this can be made even simpler by using the Array.reduce function. Conceptually, you are taking an array and producing a single function from that array, and each successive function generated should be dependant upon the last function generated. This is the problem that reduce was designed to help solve:
function js_load(resources, done) {
var queue = resources.reduceRight(function(done, r) {
return function() {
var x = document.body.appendChild(document.createElement('script'));
x.src = r;
console.log("loading "+r);
x.onload = function() {
console.log("js_load: loaded "+r);
done();
};
};
}, done);
queue();
};
Note that reduce and reduceRight are not available for older browsers (<= IE8). A JavaScript implementation can be found on the MDN page.

Categories

Resources