Populating local array from callback: synchronization issues? - javascript

Here's the deal:
I populate a local array in a callback like so:
var datasource = datasources[id];
var contexts = [];
datasource.data($selected.parent().data(), function (items) {
var dataarr = items.data;
for (var i = 0; i < dataarr.length; ++i) {
contexts.push(dataarr[i]);
}
});
foo(contexts);
Now, in foo, I run a simple check like:
function foo(contexts) {
if (contexts.length < 2) {
return;
}
}
If I break at the return statement above, contexts.length is in fact, greater than 2. Similarly, if I run the code step by step in a debugger, everything works as expected.
This make me suspect that when not running in a debugger, this code is being executed before the callback has completed.
Fine. But how can I control the execution order? Or, perhaps, is there a better paradigm to go about this if the only way I know can acquire items.data is from within that callback?
Thanks!

I'm not sure about the context here a little bit, but if you want your method foo to be called after the array has been populated, why not call it after the for loop? like so:
datasource.data($selected.parent().data(), function (items) {
var dataarr = items.data;
for (var i = 0; i < dataarr.length; ++i) {
contexts.push(dataarr[i]);
}
foo(contexts);
});
if you're uncomfortable with that, look at PubSubJS which allows you to emit events and handle them asynchronously. I wrote an article about it's usage here.
Edit: An example:
datasource.data($selected.parent().data(), function (items) {
var dataarr = items.data;
for (var i = 0; i < dataarr.length; ++i) {
contexts.push(dataarr[i]);
}
PubSub.publish('contextsPopulated', contexts);
});
and then modify foo as:
function foo(message, contexts) {
if (contexts.length < 2) {
return;
}
}
now register foo to be called whenever 'contextsPopulated' is signalled.
PubSub.subscribe('contextsPopulated', foo);

Related

How to pass an object's method as a parameter to another function in Javascript

First take a look at my simple codes below:
function mySecondFunction(objArray,setFunc)
{
for (let i = 0; i < objArray.length; i++)
{
objArray[i].info.setTop(72);
}
}
function myFunction()
{
let myObjArray = [];
for (let i = 0; i < 10; i++)
{
myObjArray.push({
info:{topVar:0,
bottomVar:0,
get top() {return this.topVar;},
get bottom() {return this.bottomVar;},
setTop: function(input) {this.topVar = input;},
setBottom: function(input) {this.bottomVar = input; }
}
});
}
mySecondFunction(myObjArray); // This works Fine
mySecondFunction(myObjArray,setTop); // I want something like this!!!
}
As you can see, I want to pass a method of an object to another function. I know a lot of possible solutions to avoid this, but I want to know whether it is possible or not.
Detach it and pass as an argument. Remember to use call to set the intended this value.
function mySecondFunction(objArray, setFunc)
{
for (let i = 0; i < objArray.length; i++)
{
setFunc.call(objArray[i].info, 72);
/* explicitly telling that:
please set 'this' value in this function to be 'objArray[i].info' when running,
allowing, e.g. `this.topVar` in
`setTop: function(input) {this.topVar = input;}`
to be operating on `objArray[i].info.topVar` */
}
}
function myFunction()
{
let myObjArray = [];
for (let i = 0; i < 10; i++)
{
myObjArray.push({
info:{topVar:0,
bottomVar:0,
get top() {return this.topVar;},
get bottom() {return this.bottomVar;},
setTop: function(input) {this.topVar = input;},
setBottom: function(input) {this.bottomVar = input; }
}
});
}
mySecondFunction(myObjArray, myObjArray[0].info.setTop);
/* once detaching the method from the object,
(if we are not using arrow functions),
we lose 'this' value, meaning we are losing
the target of object that we want to operate on */
console.log(myObjArray)
}
myFunction();
You can target item number in the array list. You can do statically (i.e. 1-???) or dynamically with an iteration and a variable. You can then the object property within that. For example:
myObjArray[0].info.setTop
That will target the 1st item in the array. Be sure to omit parentheses (()) when passing the method as you want to pass the function reference not the result

Use closure inside array with pure javascript

I need to copy a string inside an array to a value inside another array that is created in a loop. In the end when I print all names are the last in the array of names. I want to copy/clone the value so that I don't have a reference and I would like it to be only in native javascript without external libraries.
This is my code
var exp_names =["name1","name2","name3"];
var i;
for (i = 0; i < exp_names.length; i++) {
d3.tsv("data/"+exp_names[i], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[i];
});
});
});
And then all expId are "name3"
Data is loading correctly per file.
I have tried with jquery's extend function and also lodash's clone function, I have tried my own clone function and nothing works it will still throw "name3" for all the expId.
These didn't work:
var newname = new String(exp_names[i]);
var newname = $.extend(true, {}, exp_names[i]);
var newname = $.extend( {}, exp_names[i]);
var newname = _.clone(exp_names[i]);
var newname = exp_names[i].slice(0);
I am desperate by now.
You need to use bind function.
var exp_names =["name1","name2","name3"];
var i;
var func = [];
for (i = 0; i < exp_names.length; i++) {
func[i]=(function(index){
d3.tsv("data/"+exp_names[index], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[index];
});
});
}).bind(this,i);
}
for(i = 0; i < 3; i++){
func[i](i);
}
Another solution is to use let keyword.
ES6 provides the let keyword for this exact circumstance. Instead of using closures, we can just use let to set a loop scope variable.
Please try this:
for (let i = 0; i < exp_names.length; i++) {
d3.tsv("data/"+exp_names[i], function(data) {
data.forEach(function(d){
//Do stuff with my tsv
d.expId = exp_names[i];
});
});
}
I guess usage of IIFE and bind together, in the first answer is a little weird. It's best to choose either one of them. Since in the newest versions of the browsers bind is way faster than an IIFE closure and the let keyword I might suggest you the bind way.
A similar example to your case might be as folows;
var exp_names = ["name1","name2","name3"],
lib = {doStg: function(d,cb){
cb(d);
}
},
data = [{a:1},{a:2},{a:3}];
for (i = 0; i < exp_names.length; i++) {
lib.doStg(data, function(i,d) {
d.forEach(function(e){
//Do stuff with doStg
e.expId = exp_names[i];
console.log(e);
});
}.bind(null,i));
}

Cannot access array elements properly within setTimeout

Here what I'm trying to do.
I'm having an array like the following
var my_array = ['1', '2', '3' ... ,'1000000000000000'];
What I want to do is create a bunch of HTML elements for every element of that array, and since the array can contain a huge number of elements I attempted to do the following so the browser won't freeze.
for(var i in my_array)
{
if(my_array.hasOwnProperty(i))
{
setTimeout(function(){
do_something_with_data(my_array[i]);
});
}
}
What happens though is that the my_array[i] within the setTimeout doesn't have the value it should.
To be more accurate, when I try to console.log(my_array[i]) what I get is something like this:
"getUnique" function (){
var u = {}, a = [];
for(var i = 0, l = this.length; i < l; ++i){
if(u.hasOwnProperty(this[i])) {
continue;
}
a.push(this[i]);
u[this[i]] = 1;
}
return a;
}
getUnique is a function I've added to the Array prototype just like this:
Array.prototype.getUnique = function(){
var u = {}, a = [];
for(var i = 0, l = this.length; i < l; ++i){
if(u.hasOwnProperty(this[i])) {
continue;
}
a.push(this[i]);
u[this[i]] = 1;
}
return a;
};
Can please somebody help me with this issue?
the setTimeout is executed after the loop is done, and i is the last key or some garbage value at that point. You can capture the i like so:
for (var i in my_array) {
if (my_array.hasOwnProperty(i)) {
(function(capturedI) {
setTimeout(function() {
do_something_with_data(my_array[capturedI]);
});
})(i);
}
}
You should also not use for..in loops for arrays because it's an order of magnitude slower (especially so with the .hasOwnProperty check) than a for loop and the iteration order is not defined
If you have jQuery or willing to add some extra code for older browsers, you can do:
my_array.forEach( function( item ) {
setTimeout( function() {
do_something_with_data( item );
}, 1000);
});
With jQuery:
$.each( my_array, function( index, item ) {
setTimeout( function() {
do_something_with_data( item );
}, 1000);
});
See docs for [].forEach
The problem is that the functions you're creating have a reference to the i variable, not a copy of its value, and so when they run they see i as it is at that point in time (past the end of the array, presumably). (More: Closures are not complicated)
I'd recommend a completely different approach (below), but first, let's look at how to make your existing approach work.
To do what you were trying to do, with the for loop, you have to have the functions close over something that won't change. The usual way to do that is to use a factory function that creates the timeout functions such that they close over the argument to the factory. Or actually, you can pass in the array element's value rather than the index variable.
for(var i in my_array)
{
if(my_array.hasOwnProperty(i))
{
setTimeout(makeFunction(my_array[i]));
}
}
function makeFunction(entry) {
return function(){
do_something_with_data(entry);
};
}
But, I would probably restructure the code so you're not creating masses and masses of function objects unnecessarily. Instead, use one function, and have it close over an index that it increments:
// Assumes `my_array` exists at this point, and that it
// has at least one entry
var i = 0;
setTimeout(tick, 0);
function tick() {
// Process this entry
if (my_array.hasOwnProperty(i)) {
do_something_with_data(my_array[i]);
}
// Move to next
++i;
// If there are any left, schedule the next tick
if (i < my_array.length) {
setTimeout(tick, 0);
}
}
Its just a guess. Try it like:
for(var i in my_array)
{
if(my_array.hasOwnProperty(i))
setTimeout("do_something_with_data('"+my_array[i]+"')", 500);
}

Javascript async loop inside another async loop

I have some code in jQuery that iterate through children in div using each().
Every text inside is splitted into words. Each word is processed with 'for' loop.
This function can take a long time and can freeze the browser so...
Is there a way to create asynchronous loop inside another asynchronous loop but one is waiting for other to finish?
Could anyone tell me the right direction?
I came up with something like this:
var queue = [];
var nlevel = 0;
function work() {
nlevel = queue.length-1;
var arr = queue[nlevel][0];
var process = queue[nlevel][1];
var cnt = queue[nlevel][2];
var item = arr[cnt];
process.apply(item);
cnt++;
queue[nlevel][2] = cnt;
if (cnt < arr.length) {
setTimeout(work, 1);
} else {
if (queue.length>1) {
queue.pop();
setTimeout(work, 1);
}
}
}
function each(arr, process) {
queue.push([arr, process, 0]);
setTimeout(work, 1);
}
each(['one', 'two', 'three'], function() {
alert(this);
each([1, 2, 3, 4], function() {
alert(this);
});
});
but It has some major bug and I couldn't fix it.
You can use Web Workers to run multiple scripts in background threads. But they are not supported in every browsers. See this article from Mozilla or simple ask Google: https://developer.mozilla.org/En/Using_web_workers
You can use SetTimeout(0,...) periodically to "yield" control to the browser to prevent freezing the browser (but it will not execute any faster, in fact it will probably be slightly slower).
See this answer for an example of the technique, I can't be more specific without seeing your code.
One way you could do this is to create a queue of work items to be processed:
var queue = [];
Place items to be processed in this queue instead of processing right away:
queue.push(item);
Then start a timer loop for processing items:
var delayBetweenItems = 10;
var processItemFromQueue = function() {
if (queue.length) {
item = queue.shift();
doStuff(item);
setTimeout(delayBetweenItems, processItemFromQueue);
}
};
setTimeout(processItemFromQueue, delayBetweenItems);
Assuming that your current code is something similar to this:
function processWord(word, i, j) {
// do something with the current word, where
// (if it's needed) i is the index into the elements
// and j is the index into the current element's words
}
$("#divId").children().each(function(i) {
var words = $(this).text().split(" "),
j;
for(j = 0; j < words.length; j++) {
processWord(words[j], i, j);
}
});
You can rewrite that to do both the outer (i) and inner (j) loops with setTimeout():
// assumes the same processWord() function as above
(function (){
var $items = $("#divId").children(),
words,
i, j,
iMax, jMax;
function doIteration() {
if (j === jMax) {
if (++i === iMax)
return;
// outer loop processing
words = $($items[i]).text().split(" ");
jMax = words.length;
j = 0;
}
if (j < jMax) {
// inner loop processing
processWord(words[j], i, j);
j++;
}
setTimeout(doIteration, 1);
}
iMax = $items.length;
i = j = jMax = -1;
doIteration();
})();
Working demo: http://jsfiddle.net/sp8Wr/
The doIteration() function simulates a nested loop by processing some counters appropriately and calling itself via setTimeout() to do the next iteration. The immediately-executed-anonymous-function wrapping the whole thing is not essential to the process, it is there just to restrict the scope of the variables.
Yes this could probably be done much better, but this is just what I came up with on the fly - obviously you'll modify it as appropriate for your own processing.
(If you don't care what order the words get processed in as long as the processWord() function gets the correct values of i and j associated with each word this can easily be made much tidier, but I don't have time to do a neater version that processes in order.)

Update happens only on the last row, instead of first

function createTextFields(obj) {
for (var i = 0; i < obj.length; i++) {
var dataDump = {};
for (var key in obj[i]) {
var textField = Ti.UI.createTextField(pm.combine($$.labelBrown, {
left: 200,
height:35,
value:obj[i][key],
width:550,
keyboardType:Ti.UI.KEYBOARD_NUMBER_PAD,
layout:'horizontal',
backgroundColor:'transparent',
id:i
}));
dataDump[key] = textField.value;
var callback = function (vbKey) {
return function (e) {
dataDump[vbKey] = e.source.value;
};
}(key);
}
globalData.push(dataDump);
}
}
I am using the simlar code for Adding the data and it works fine. I posted the problem yesterday and it got resolved...
Last Object is always getting updated?
Now when i go to edit page, it shows me four text fields or number of text fields added... now when i edit something and click on save... the value get's updated on the fourth or the last TextFields Object...
Don't define functions inside loops. Computationally expensive and leads to problems, like this one. Here's a fix that should solve it:
function createTextFields(obj) {
var callback = function (vbKey, localDump) {
return function (e) {
localDump[vbKey] = e.source.value;
};
}
var i;
var max = obj.length;
for (i = 0; i < max; i++) {
var dataDump = {};
for (var key in obj[i]) {
dataDump[key] = textField.value;
var callBackInstance = function(keyn, dataDump);
}
globalData.push(dataDump);
}
}
JavaScript does not have block level scope, so your variables dataDump and callback, though "declared" inside for-loops actually belong to the function. As in, you're saving a value to dataDump, then you're overwriting it, each time you go through the loop. Which is why finally only the code that operated on the last value remains.
Take a look at What is the scope of variables in JavaScript? too.

Categories

Resources