Cannot access array elements properly within setTimeout - javascript

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

Related

Understanding callback.call function in jquery

Please go through the below code.
I am not able to exactly what's calllback.call is doing.
Also, I am not able to know what is the difference between this and this[i] and how if(callback.call(this, this[i])) is evaluated to true or false.
Array.prototype.each = function(callback) {
var i = 0;
while (i < this.length) {
callback.call(this, this[i]);
i++;
}
return this;
};
Array.prototype.map = function(callback) {
var i = this.length;
var found = []
while (i--) {
if(callback.call(this, this[i])) {
found.push(this[i]);
}
}
return found;
};
The functions are called below:
Array.each(function(value){
...
})
Array.map(function(value){
...
})
I am not able to exactly what's calllback.call is doing.
Function.prototype.call lets you invoke a function with an explicit value for this
callback.call(this, this[i]);
is the same as
callback(this[i])
except that inside the callback the value of this is the set to be the same as it was in the current context.
Also am not able to know what is the difference between this and this[i].
In this context, this is the current array. So this means the whole array, this[i] gets the i'th element of the array.
The .each function you have will loop through an array and call a function for each of them. This is similar to javascript's built in Array.prototype.forEach.
The .map is named as though it were a polyfill for Array.protoype.map but is actually doing a .filter operation. Strange.

Asyncronous variables outside a for loop

I'm just starting out with AJAX and I'm trying to get a variable to be set inside a for loop. Then I want to call that variable later and use it's value.
Of course this would be synchronous, requiring the scripts to stop executing in order to run the loop before returning the new value of the function.
I'm hoping someone knows a better way to get the value from the for loop AFTER the for loop has run and use it in my code directly after that.
I would prefer not to use the setTimeout() hack to bypass this issue (it is a hack after all).
var getCount = function getCount(res) {
count = { active: 0, closed: 0 }; //Variable defined here
for(i=0; i<=res.length; i++) {
if(res[i].status == 'active') {
count.active ++;
} else { count.closed ++; }
}
return count; //And returned here
};
getCount(result);
console.log(count); //Here's where I need the result of the for loop
//Currently this outputs the count object with both properties set to 0;
I am not sure what AJAX has to do with your issue.
You are not assigning the result of the getCount function to the count variable (Unless you intended the count variable to be global, but in that case you need to define it before the getCount function definition).
Change this line:
getCount(result);
to this:
var count = getCount(result);
And you should be alright. :)
I would also suggest, when declaring variables, always declare them with var. In your case:
var count = { active: 0, closed: 0};
I don't know why you mention AJAX since there is nothing async about your code.
From what I see in your sample I don't see what all the difficulty is about.
Just use it as any other function.
function getCount(res) {
var count = { active: 0, closed: 0 }; //Variable defined here
for(i=0; i<=res.length; i++) {
if(res[i].status == 'active') {
count.active ++;
} else { count.closed ++; }
}
return count; //And returned here
};
console.log(getCount(result)); //Here's where I need the result of the for loop
First off, you had an extra = sign that was over-extending your for loop. I don't know if this answers your asynchronous issue, but here is how I would do it:
// sample object
var result = [
{status:"active"},
{status:"not-active"},
{status:"active"}
];
// kick off the function to get the count object back
var counts = getCount(result);
console.log(counts);
function getCount(res) {
var count = { active: 0, closed: 0 }; //Variable defined here, make sure you have var to keep it from going global scope
for(i=0; i<res.length; i++) { //here you had a wrong "="
if(res[i].status === 'active') {
count.active ++;
} else { count.closed ++; }
}
return count; //And returned here
}
Example here.

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.)

Can I shrink this code with an array or loop?

Forgive me, I hope this question isn't too obvious, I'm a javascript noob.
I have javascript code that takes numbers from an xml sheet and displays them in td elements on an html page. It works but I think it could be condensed into an array or a loop to be more efficient.
Is there a better way to write this code?
window.onload=function displayPrices()
{
twentyFourK=(x[i].getElementsByTagName("twentyFourK")[0].childNodes[0].nodeValue);
document.getElementById("twentyFourK").innerHTML=toCurrency(twentyFourK);
oneOzGold=(x[i].getElementsByTagName("oneOzGold")[0].childNodes[0].nodeValue);
document.getElementById("oneOzGold").innerHTML=toCurrency(oneOzGold);
fiveOzGold=(x[i].getElementsByTagName("fiveOzGold")[0].childNodes[0].nodeValue);
document.getElementById("fiveOzGold").innerHTML=toCurrency(fiveOzGold);
tenOzGold=(x[i].getElementsByTagName("tenOzGold")[0].childNodes[0].nodeValue);
document.getElementById("tenOzGold").innerHTML=toCurrency(tenOzGold);
oneKiloGold=(x[i].getElementsByTagName("oneKiloGold")[0].childNodes[0].nodeValue);
document.getElementById("oneKiloGold").innerHTML=toCurrency(oneKiloGold);
//etc.
}
Yes, a function could make things much easier for you:
window.onload = function() {
function loadCurrency(name) {
document.getElementById(name).innerHTML = toCurrency(x[i].getElementsByTagName(name)[0].firstChild.nodeValue);
}
loadCurrency('twentyFourK');
loadCurrency('oneOzGold');
loadCurrency('fiveOzGold');
loadCurrency('tenOzGold');
loadCurrency('oneKiloGold');
};
Also, if you have many items to load:
window.onload = function() {
function loadCurrency(name) {
document.getElementById(name).innerHTML = toCurrency(x[i].getElementsByTagName(name)[0].firstChild.nodeValue);
}
var items = ['twentyFourK', 'oneOzGold', 'fiveOzGold', 'tenOzGold', 'oneKiloGold'];
items.forEach(loadCurrency);
};
That requires Array.forEach, which is only available in ECMAScript 5, so here's a fallback:
Array.prototype.forEach = function(action, thisArg) {
for(var i = 0, l = this.length; i < l; i++) {
if(i in this) {
action.call(thisArg, this[i], i, this);
}
}
};
I would place the currency setting into its own method. This will be cleaner visually and will also allow for implementation changes in the future:
window.onload = function displayPrices() {
SetCurrency("twentyFourK");
SetCurrency("oneOzGold");
//etc.
}
function SetCurrency(name) {
var elements = x[i].getElementsByTagName(name);
if ((elements != null) && (elements.length != 0)) {
elements[0].innerHTML = toCurrency(elements[0].childNodes[0].nodeValue);
}
}
You could create a function with a list of element in parameter, and you just have to create a loop going through your list of elements (twentyFourK, oneKiloGold and so on)

Weird Event Listening in ActionScript3

I have a weird quirk in ActionScript. I need to pass the index to a callback function.
Here is my code
for (var i:Number = 0; ((i < arrayQueue.length) && uploading); i++)
{
var lid:ListItemData=ListItemData(arrayQueue[i]);
var localI:Number= new Number(i); // to copy?
var errorCallback:Function = function():void { OnUploadError(localI); };
var progressCallback:Function = function(e:ProgressEvent):void { lid.progress = e; OnUploadProgress(localI); };
var completeCallback:Function = function():void { Alert.show('callback'+localI.toString()); OnUploadComplete(localI); }; // localI == arrayQueue.length - 1 (when called)
Alert.show(localI.toString()); // shows current i as expected
lid.fileRef.addEventListener(Event.COMPLETE, completeCallback);
lid.fileRef.addEventListener(ProgressEvent.PROGRESS, progressCallback);
lid.fileRef.addEventListener(HTTPStatusEvent.HTTP_STATUS, errorCallback);
lid.fileRef.addEventListener(IOErrorEvent.IO_ERROR, errorCallback);
lid.fileRef.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorCallback);
lid.fileRef.upload(url, 'File');
}
Any idea on how to pass in the index to my callbacks? .upload does not block.
Passing additional parameters for your callbacks is possible via some kind of delegate function or closure. However it is often considered a bad practice. You may use event target property instead to determine your index based on FileReference.
Edit: here is a sample of using closures:
function getTimerClosure(ind : int) : Function {
return function(event : TimerEvent) {
trace(ind);
};
}
for (var i = 0; i < 10; i++) {
var tm : Timer = new Timer(100*i+1, 1);
tm.addEventListener(TimerEvent.TIMER, getTimerClosure(i));
tm.start();
}
This will continuously trace numbers from 0 to 9.
Edit2: here is a sample of creating a delegate based on a function closure:
function timerHandler(event : Event, ...rest) : void {
trace(event, rest);
}
function Delegate(scope : Object, func : Function, ...rest) : Function {
return function(...args) : void {
func.apply(scope, args.concat(rest));
}
}
var tm : Timer = new Timer(1000, 1);
tm.addEventListener(TimerEvent.TIMER, Delegate(this, this.timerHandler, 1, 2, 3));
tm.start();
However this is a bad approach since unsubscribing for such a listener is a hell pain. This in turn will probably cause some memory leakages, which will decrease overall performance of your application. So, use with caution!
Bottom line: if you know how to work with closures, use them - it is a wonderful thing! If you don't care about your application performance in a long perspective, use closures - it's simple!
But if you are unsure about closures, use a more conventional approach. E.g. in your case you could create a Dictionary that matches your FileReference objects to appropriate indices. Something like that:
var frToInd : Dictionary = new Dictionary(false);
// false here wouldn't prevent garbage collection of FileReference objects
for (var i : int = 0; i < 10; i++) {
// blah-blah stuff with `lib` objects
frToInd[lib.fileRef] = i;
// another weird stuff and subscription
}
function eventListener(event : Event) : void {
// in the event listener just look up target in the dictionary
if (frToInd[event.target]) {
var ind : int = frToInd[event.target];
} else {
// Shouldn't happen since all FileReferences should be in
// the Dictionary. But if this happens - it's an error.
}
}
-- Happy coding!
I have a weird quirk in ActionScript
It's not a quirk, it's variable scope. You should read this article: http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f9d.html#WS5b3ccc516d4fbf351e63e3d118a9b90204-7f8c
And you really shouldn't use anonymous, it just makes everything more confusing. You're actually making multiple copies of the same object.
If the arrayQueue is in scope, you can use this code to get the index:
GetArrayIndex(e.currentTarget);
function GetArrayIndex(object:Object):Number
{
for(var i:Number = 0; 0 < arrayQueue.length; i++)
{
if(object === arrayQueue[i])
return i;
}
}
You should consider using an uint for the index.

Categories

Resources