Having some issues with my non-blocking Javascript - javascript

this.plotted = [jQuery('#img1'), jQuery('#img2'), jQuery('#img3')];
Blah.prototype.animate = function()
{
if (!this.plotted.length)
throw 'Blah::animate - No points have been plotted';
// fix the scope
var _this = this;
var animateOn = function(image)
{
image.attr('src', _this.options.pointActive);
setTimeout(function() { animateOff(point); }, 700);
}
var animateOff = function(image)
{
image.attr('src', _this.options.pointDefault);
setTimeout(function() { animateOn(point); }, 700);
}
for (var i = 0; i < this.plotted.length; i++)
{
var point = this.plotted[i];
setTimeout(function() { animateOn(point); }, 700);
}
}
I'm trying to animate these 3 images by switching their src between an 'on' and 'off' image. I don't want this to be 'sequential'. Ie, I don't want to see the first image change, then the second and then the third.
I'm using setTimeout to accomplish this. Well, I'm trying to...
Firstly, the problem I'm having is with the setTimeout inside the for loop.
for (var i = 0; i < this.plotted.length; i++)
{
var point = this.plotted[i];
console.log(point); // this correctly shows each image point
setTimeout(function()
{
console.log(point); // this shows the same, first, point
animateOn(point);
}, 700);
}
I have no idea what the inner point isn't matching the outer point :/
Also, I would like to know if this method is, well, stupid. Will these nested function calls continually build onto the stack and eventually cause me to run out of RAM? Is there a better way to approach this?

This doesn't work because of how closures work.
I'd do it like this:
var makeAnimateStarter = function(point) {
return function() {
animateOn(point);
};
};
for (var i = 0; i < this.plotted.length; i++)
{
var point = this.plotted[i];
setTimeout(makeAnimateStarter(point), 700);
}
And it's not a problem from a stack point of view. Every time a timeout is executed, it's in a new call stack. That's why you require _this. setTimeout() is not suspending the thread at that point and then resuming it's executing the function fresh.

Related

Int from for loop not working in function - JS

I am pretty new to Javascript and have noticed this odd issue come up.
var dispatchMouseEvent = function(target, var_args) {
var e = document.createEvent("MouseEvents");
e.initEvent.apply(e, Array.prototype.slice.call(arguments, 1));
target.dispatchEvent(e);
};
var Level1Cats = document.getElementsByClassName("p-pstctgry-lnk-ctgry "); //GETTING LEVEL 1 CATS
var Level1CatsLen = Level1Cats.length; //GETTING LEVEL 1 CAT LEN
for (i = 0; i <= Level1CatsLen-1; i++) {
var ID1 = Level1Cats[i].id;
var temp1 = Level1Cats[i].innerHTML;
temp1.replace(/&/gi, "&").replace(/<[^>]*>/gi, "");
function GoToLevel2(callback) { //GO TO NEXT LEVEL!
dispatchMouseEvent(Level1Cats[i], "mouseover", true, true);
dispatchMouseEvent(Level1Cats[i], "click", true, true);
}
function GetLevel2() { //GET NEXT LEVEL
var Level2Cats = document.getElementsByClassName("p-pstctgry-lnk-ctgry");
return Level2Cats.length;
}
setTimeout(function() { //RUN IT WITH TIMING
GoToLevel2();
}, 100);
var Level2CatsLen = GetLevel2();
}
When the code is executed it gives me an error (Cannot read property 'dispatchEvent' of undefined)
I know this is because the i in the function does not seem to work. If I simply replace it with an int value of 1 it will execute and click cat 1, 16 times, as expected..
I would have thought this should work, any ideas how I can work around it?
Inside the loop, a closure GoToLevel2 is created, closing over the variable i, when i is 1. Then the loop runs through, i is incremented to 2, and the loop is terminated.
Then your setTimeout fires after 100ms, and invokes your closure. It still remembers that there was once a variable i, but it now contains 2. Level1Cats[2] is undefined, and you get an error.
The standard solution is to enclose the contents of the loop into another self-evaluating function that will not close over i:
for (i = 0; i <= Level1CatsLen-1; i++) {
(function(i) {
// ...
})(i);
}
(Note also that setTimeout(function() { GoToLevel2(); }, 200) is identical to, but less efficient than setTimeout(GoToLevel2, 200).)

JavaScript - for loop iteration delay for stacked images

I have a "deck" of images, each one with a different Z-index. The top "cards" will go below the card with the lowest Z-index (let's call it "image1") in succession, until "image1" appears on top of the others. This is for an animation, but I can't get the for loop to iterate with a set time. I want to try 30 images, for now, in 1 second. Here is the code:
function placeImage(x) {
var div = document.getElementById("div_picture_right");
div.innerHTML = ""; // clear images
for (counter=1;counter<=x;counter++) {
var image=document.createElement("img");
image.src="borboleta/Borboleta"+counter+".png";
image.width="195";
image.height="390";
image.alt="borboleta"+counter;
image.id="imagem"+counter;
image.style.backgroundColor="rgb(255,255,255)"
image.style.position="absolute";
image.style.zIndex=counter;
div.appendChild(image);
}
};
var animaRight = function(x) {
var imageArray = [];
for (counter=1;counter<=x;counter++) {
imageArray[counter-1] = document.getElementById("imagem"+counter);
}
function delayLoop(x) {
for (counter=imageArray.length;counter>1;counter--) {
if (imageArray[counter-1].style.zIndex==counter) {
imageArray[counter-1].style.zIndex-=counter;
setTimeout("delayLoop()", 1000/x);
}
}
}
delayLoop(x);
};
Any help would me much appreciated! I tried some setTimeout functions, but I'm afraid I'm doing it wrong (placement, maybe?). If you need to, I'll edit the code with what I tried.
Is that what you want ?
var animaRight = function(x) {
var imageArray = [];
for (counter=1;counter<=x;counter++) {
imageArray.push(document.getElementById("imagem"+counter));
}
for (var i=0; i<x; i++) {
(function(i) {
setTimeout(function(){
imageArray[i%imageArray.length].style.zIndex=i*-1
}, i*1000);
})(i);
}
};
If it does what you want, I'll explain it more.

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 for loop goes over loop constraint, possible misunderstanding of closure

Basically I have some event listeners and their handling function defined as follows:
<div id="postTextBlock"/>
<div id="postImageBlock"/>
<div id="postQuoteBlock"/>
<div id="postLinkBlock"/>
document.getElementById('postTextBlock').addEventListener('click', function() { showPostType(postTextBlock) }, false);
document.getElementById('postImageBlock').addEventListener('click', function() { showPostType(postImageBlock) }, false);
document.getElementById('postQuoteBlock').addEventListener('click', function() { showPostType(postQuoteBlock) }, false);
document.getElementById('postLinkBlock').addEventListener('click', function() { showPostType(postLInkBlock) }, false);
var showPostType = (function () {
var postTypes = new Array('postTextBlock', 'postImageBlock', 'postQuoteBlock', 'postLinkBlock')
return function(type) {
for (var i = 0; i < postTypes.length; i++) {
(function(index) { alert(document.getElementById(postTypes[index])) })(i)
}
}
})()
When I run this I will get 5 alerts. One for each of the postTypes defined in my array and a final null for what I'm guessing is postTypes[5]. Why is it executing the code with i = 5 when I have set the for loop to terminate when i = 5 (postTypes.length = 4).
Edit:
I added the html that it references as well as the full array values. Hopefully this clears some stuff up about the code not working.
You know your code sample doesn't work? I took a stab at what it's --supposed-- to do.
http://jsfiddle.net/8xxQE/1/
document.getElementById('postTextBlock').addEventListener('click', function() {
showPostType('postTextBlock'); //Argument does nothing
}, false);
document.getElementById('postImageBlock').addEventListener('click', function() {
showPostType('postImageBlock'); //Argument does nothing
}, false);
The arguments passed above were not included, based on the function code they did nothing anyways.
var showPostType = (function() {
var postTypes = new Array('postTextBlock', 'postImageBlock')
return function(/*type argument removed isn't referenced*/) {
var l = postTypes.length;
for (; l--;) {
(function(index) {
console.log(index, postTypes[index]);
alert(document.getElementById(postTypes[index]))
})(l);
}
}
})()
I added some trickery as just an example of a better way to write a for loop. Your closure works fine, I think you are doing something else to cause this code to not work as expected. Why would this error run 4 times, there's only two items in the array. My example ran exactly twice every time I clicked a div, as you can see on JSFiddle.
The div's id is "postLInkBlock", but you're searching for "postLinkBlock". That's the null.

How to stop all timeouts and intervals using javascript? [duplicate]

This question already has answers here:
javascript: Clear all timeouts?
(13 answers)
Closed 6 years ago.
I'm working on an ajax web appliation which contains many running timeouts and intervals. And now I need to clear all running timeouts and intervals sometimes. Is there a simple way to stop everything without need to store every timeout and interval ID and iterate through them and clear them?
Sometimes it's possible to save the timer Id / Handle to clear it later which would be the best solution. So this is a second best. But I wanted to give a better understanding of what's going on. It basically grabs the highest timer id and clears everything less than that. But it's also possible to clear other timers that you do not want to clear!
It is a little hackish, so be warned!
// Set a fake timeout to get the highest timeout id
var highestTimeoutId = setTimeout(";");
for (var i = 0 ; i < highestTimeoutId ; i++) {
clearTimeout(i);
}
Updated answer after reading the duplicate I closed this question with -
It works and tested in Chrome on OSX
// run something
var id1 = setInterval(function() { console.log("interval", new Date())}, 1000);
var id2 = setTimeout(function() { console.log("timeout 1", new Date())}, 2000);
var id3 = setTimeout(function() { console.log("timeout 2", new Date())}, 5000); // not run
setTimeout(function() { console.log("timeout 3", new Date())}, 6000); // not run
// this will kill all intervals and timeouts too in 3 seconds.
// Change 3000 to anything larger than 10
var killId = setTimeout(function() {
for (var i = killId; i > 0; i--) clearInterval(i)
}, 3000);
console.log(id1, id2, id3, killId); // the IDs set by the function I used
NOTE: Looked at window objects that had a typeof number - funnily enough IE assigns an 8 digit number, FF a single digit starting with 2
Here is a workaround.
window.timeoutList = new Array();
window.intervalList = new Array();
window.oldSetTimeout = window.setTimeout;
window.oldSetInterval = window.setInterval;
window.oldClearTimeout = window.clearTimeout;
window.oldClearInterval = window.clearInterval;
window.setTimeout = function(code, delay) {
var retval = window.oldSetTimeout(code, delay);
window.timeoutList.push(retval);
return retval;
};
window.clearTimeout = function(id) {
var ind = window.timeoutList.indexOf(id);
if(ind >= 0) {
window.timeoutList.splice(ind, 1);
}
var retval = window.oldClearTimeout(id);
return retval;
};
window.setInterval = function(code, delay) {
var retval = window.oldSetInterval(code, delay);
window.intervalList.push(retval);
return retval;
};
window.clearInterval = function(id) {
var ind = window.intervalList.indexOf(id);
if(ind >= 0) {
window.intervalList.splice(ind, 1);
}
var retval = window.oldClearInterval(id);
return retval;
};
window.clearAllTimeouts = function() {
for(var i in window.timeoutList) {
window.oldClearTimeout(window.timeoutList[i]);
}
window.timeoutList = new Array();
};
window.clearAllIntervals = function() {
for(var i in window.intervalList) {
window.oldClearInterval(window.intervalList[i]);
}
window.intervalList = new Array();
};
It works for set/clear timeout/interval functions called after these lines are executed. Try and see it works:
setInterval('console.log(\'a\')', 1000);
setInterval('console.log(\'b\')', 500);
setInterval('console.log(\'c\')', 750);
setTimeout('clearAllIntervals()', 10000);
Proxying does the magic.
var noofTimeOuts = setTimeout('');
for (var i = 0 ; i < noofTimeOuts ; i++) clearTimeout(i);
var max = setTimeout(function(){ /* Empty function */ },1);
for (var i = 1; i <= max ; i++) {
window.clearInterval(i);
window.clearTimeout(i);
if(window.mozCancelAnimationFrame)window.mozCancelAnimationFrame(i); // Firefox
}
There's nothing built-in, but it's pretty easy to blast through all currently outstanding deferred execution functions by calling this clearAll() function:
function clearAll() {
for (var i = setTimeout(function() {}, 0); i > 0; i--) {
window.clearInterval(i);
window.clearTimeout(i);
if (window.cancelAnimationFrame) window.cancelAnimationFrame(i);
}
}
If you are in charge of the page you run, and can wrap the native deferred execution functions in wrappers that do the house keeping for of course equip each setter function with a corresponding .clearAll() too:
(function(deferFunctions) {
for (var setter in deferFunctions) (function(setter, clearer) {
var ids = [];
var startFn = window[setter];
var clearFn = window[clearer];
function clear(id) {
var index = ids.indexOf(id);
if (index !== -1) ids.splice(index, 1);
return clearFn.apply(window, arguments);
}
function set() {
var id = startFn.apply(window, arguments);
ids.push(id);
return id;
}
set.clearAll = function() { ids.slice(0).forEach(clear); };
if (startFn && clearFn) {
window[setter] = set;
window[clearer] = clear;
}
})(setter, deferFunctions[setter]);
})(
{ setTimeout: 'clearTimeout'
, setInterval: 'clearInterval'
, requestAnimationFrame: 'cancelAnimationFrame'
});
To try that it works, you could then try doing this, for instance, which will remain silent, as none of the callbacks end up firing before they're cancelled again:
// Some example timers of all types:
requestAnimationFrame(console.error);
setInterval(console.info, 1000, 'interval');
setTimeout(alert, 0, 'timeout');
// Now you can clear all deferred functions
// by execution type, whenever you want to:
window.setTimeout.clearAll();
window.setInterval.clearAll();
window.requestAnimationFrame.clearAll();
A little hack added to Gokhan Ozturk's answer
If you are using third party libraries which uses Timeouts and Intervals then they will also be cleared, so I added one parameter to notify function that this interval is to be push'ed or not to array.
window.setTimeout = function(code, delay, toBeAdded) {
var retval = window.oldSetTimeout(code, delay);
var toBeAdded = toBeAdded || false;
if(toBeAdded) {
window.timeoutList.push(retval);
}
return retval;
};
... // likewise for all functions.
You might be better off creating a scheduler. Take a look at this approach by Nader Zeid:
https://www.onsip.com/blog/avoiding-javascript-settimeout-and-setinterval-problems
It's an approach that help create some determinacy (because "the time interval argument of each of those functions really only establishes that the given function will execute after at least that amount of time. So a timed event can miss its target by literally any amount of time.").
Specifically, to the question you raise here, you can easily add and remove functions from the queue. While this response is long after the question was raised, hopefully it's helpful to any who find themselves struggling with Timeouts and Intervals.
You cannot clear any timeouts and intervals you don't know about.
You'd need something like getTimeoutList which isn't in the DOM3 spec, or even planned, AFAIK.
The previous proxying trick is nice, but if you have a lot of timeouts and intervals, I would not fill the arrays with consecutive numbers [1,2,3....], but with intervals. For example, instead of having [1,2,3,7,8,9], you would have maybe something like ['1-3','7-9'] or [[1,3],[7,9]], as a memory optimization. Of course this trick is only suited if you have a lot of timeouts and intervals and also if you would not stop arbitrary intervals that often.

Categories

Resources