How to stop long javascript loops from crashing the browser? - javascript

I have a button in my page that's looping a series of DIVs and editing them (appending text mostly, nothing serious),
The thing is, the number of DIVs is changing by the user (The user can add or remove them freely),
I'm looping the DIVs via jQuery $.each function:
var DomToEdit = $('.divs_to_edit');
$.each(DomToEdit, function() { $(this).append('text'); ... });
the variable DomToEdit contains somewhat unlimited number of divs, and then I refer to them via the $.each function.
Sometimes while doing the $.each loop the user gets to wait for a couple of secons, and in worse cases the browser is crashing
Is there a way to prevent this? Maybe having the loop "sleep" after 50 DIVs?
Thanks
EDIT: I didn't use the same ID, sorry - it was a flaw in my explanation. I use the same class. :)

The first argument of the function in the .each handler is the index of the current element. you can simply add a check before it, and return false to stop the loop.
$.each(DomToEdit, function(i) { // or DomToEdit.each(function() {
if (i === 50) return false;
..
DomToEdit is a jQuery object, so $.each(DomToEdit, fn) and DomToEdit.each(fn) are equivalent.
A more effective method is to cut off the elements, using .slice(0, 50).
DomToEdit.slice(0, 50).each( function(i) {
..

Add a timer which will execute append 50 div's every 5 seconds and works thru the array of div until it finishes iterating all div.
Below code works on 50 div every 5 seconds.
var DomToEdit = $('#divs_to_edit');
var timer = setInterval( function () { //<-- Create a Timer
$.each(DomToEdit, function(index) { //<-- Iterate thru divs
if (index == 50) return; //<-- Return on 50 for later
$(this).append('text');
});
DomToEdit = DomToEdit.slice(0, 50); //<-- Slice the processed div's
// Clear timer when all elements are processed.
if (DomToEdit.length == 0) {
clearInterval(timer);
}
}, 5000); // <-- Do the steps on every 5 secs

Whenever I think that code can potentially cause a crash, I'll create a self-decementing breaker variable that breaks out of a loop after a certain number of loop cycles.
var breaker = 100;
while(true) {
breaker--;if(breaker<0){console.log("Oh snap batman");break;}
console.log("CRASH");
}
The method could execute alternative code that works around the crash as well. Usually, I just try to fix the code somehow ;)

You could setTimeout to 0 in order to queue the processing of each element into the execution stack (0 makes it just queue without delay):
$.each(DomToEdit, function() {
var elem = $(this);
setTimeout(function() { elem.append('text'); }, 0);
});

You could queue the tasks and then execute tasks
in batches of X every Y milliseconds:
var queue = [];
$.each(DomToEdit, function () {
queue.push( $.proxy( function () {
$(this).append('text');
}, this ));
});
window.setInterval( function(){
var l = 100;
while( queue.length && l-- ) { //Keep executing tasks until there
//is no more or maximum amount of tasks
//executed for this batch is executed
queue.shift()();
}
}, 50 );
The real fix is of course carefully review what you are doing and fix that. $('#divs_to_edit') always returns a single element max so .each doesn't make much sense here for example...

It's possible with an extremely large number of elements, that it would actually be less processor intensive to pull the entire container element as a string and run a Javascript .replace() on it and replace the entire container, than looping through hundreds of thousands of elements?

Related

Iterate through an array of DIVs and fadeIn/fadeOut

I want to build a simple banner image slider functionality, but instead of sliding, the images are supposed to fade in and out.
It's a Drupal project, so I don't know how many images are going to be used.
The structure is simple enough:
<div class="banner-container">
<!-- potentially a lot of divs with background images -->
</div>
What I want to do is essentially iterate through this "array" of divs let the currently active one fade out and let the one that the array is pointing to at the moment fade in.
I thought I could get it to work like this:
var a = $('#banner-container').children();
while(1) {
a.each(function(){
$('.active').fadeOut(500).removeClass('active');
$(this).fadeIn(500).addClass('.active');
})
}
Obviously there is a lot wrong in my code, I have run into multiple issues, but the one I cannot seem to solve is getting the loop to wait between the iterations. To my knowledge, a traditional loop like foreach could just have some sort of wait function added to the end of it and execute that wait in every loop. But the .each() seems to execute every iteration simultaneously. How can I loop through an array like this (potentially infinitely) and wait like 5 seconds every time?
I read somewhere that the animate functions work asynchronously, and I assume the same goes for CSS transforms, so creating fadeIn, and fadeOut classes and try this with CSS wouldn't work.
You're looking for the setTimeout and setInterval functions: You pass a function into them with a timeout (in milliseconds), and they call the function after that many milliseconds (repeating, in the case of setInterval).
So for instance:
var a = $('#banner-container').children();
var index = 0;
run();
function run() {
a.filter('.active').fadeOut(500).removeClass('active');
a.eq(index).fadeIn(500).addClass('active');
index = (index + 1) % a.length; // Wraps around if it hits the end
setTimeout(run, 1000);
}
That will update the first div, then a second later the next div, and so on.
Live Example:
// Scoping function to avoid creating globals
(function() {
var a = $('#banner-container').children();
var index = 0;
run()
function run() {
a.filter('.active').fadeOut(500).removeClass('active');
a.eq(index).fadeIn(500).addClass('active');
index = (index + 1) % a.length; // Wraps around if it hits the end
setTimeout(run, 1000);
}
})();
#banner-container a {
display: none;
position: absolute;
}
#banner-container {
position: relative;
}
<div id="banner-container">
<a>one</a>
<a>two</a>
<a>three</a>
<a>four</a>
<a>five</a>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
In the above, I've used setTimeout, which only schedules a single callback, because I find I write fewer bugs when the thing I'm calling has to explicitly re-schedule the next call. :-) But you could also use setInterval(run, 1000); which would call run at roughly one-second intervals, until/unless you told it to stop via clearInterval.
you can't use a loop for this because of the nature of javascript, you will have to use recursion instead, something like this untested code should work..
function loopNext(currentIndex){
// set the current index on the first call
if('undefined' === typeof currentIndex) currentIndex=0;
// the group of image divs
var images = $(".imagesDivs");
// if the current index doesn't exist,
// we've looped thru them all, reset the index
if('undefined' === typeof images.eq(currentIndex)) currentIndex=0;
// fade out the previous one and fade in the first one
images.fadeOut('slow', function(){
images.eq(currentIndex).fadeIn('slow');
});
// fade the next one every 3 seconds or so
setTimeout(function(){
loopNext(currentIndex+1);
}, 3000);
}
To start the loop you can just call it without any parameters..
loopNext();
you can use setTimout and go to the next element or start over:
setTimout(function(){
var current = $('.active');
var next = current.next().length > 0 ? current.next() : $('.banner-container > div').first();
current.fadeOut(500).removeClass('active');
next.fadeIn(500).addClass('.active');
},5000);

Javascript - passing index to setInterval callback

The problem
I'm trying to simplify a long javascript code and i have a problem with identifying callbacks.
I have a large array with elements to animate on page
[selector, activate interval, hide after]:
things_to_move = [
['.ufo, .chefoven, .sushi', 9900, 2000],
['.hotdog,.pizzaman,.boyballon', 12090, 3600],
(...)
]
Basically, the aim is to to activate each of the selectors every x seconds, and hide them x seconds later, as per the example above.
Current code
After many tries, I ended up with this:
// Activate the element, and set timeout to hide it
var showFun = function(index1) {
$(things_to_move[index1][0]).addClass('move');
setTimeout( function(){hideFun(index1)},things_to_move[index1][2]);
}
// Hide the element
var hideFun = function(index2) {
$(things_to_move[index2][0]).removeClass('move');
}
// Loop through all items and set the interval for each one
for(_A=0; _A < things_to_move.length; _A++) {
setInterval(function(){showFun(_A)}, things_to_move[_A][1]);
}
But of course this doesn't work. Every time the showFun function is called, it takes the value of _A after the loop finished and not the value at which setInterval was set.
Question
So the question is, how can i pass a unique index into the setInterval callback, so the callback knows which array item to use?
Final solution
If anyone is interested, the final solution: Fiddle
The most direct way to solve it is using closures.
Try something like this:
for(_A=0; _A < things_to_move.length; _A++) {
setInterval((function(_innerA){
return function(){ showFun(_innerA); };
})(_A), things_to_move[_A][1]);
}

Making a total number count appear to increase

Basically I have a class counting system that displays the number of classes and displays them in a span element. Below is the code:
$.get('other.html', function(data) {
$('#total').html($('.doc', data).length);
});
This works perfectly, however I'd like a way to have the numbers increasing one by one since the span element contains 0 when the page loads. Here's an example (the numbers increasing on here).
For this I have tried setTimeout and despite this not working anyway, I released it would simply delay the function and then display the end number. I have heard of periodical or something similar being used but could not find this in the example source code.
I am really sorry for more poor phrasing. If you have no idea what I mean then just ask and I'll try rephrase or find a better example.
The key is the function which increases the number should set a setTimeout to call itself, before termination. This way it will always be called again. If you want the option to stop the incrementing, you can add a global variable, and the function will only set a new timeout when that variable is true.
Example:
var doIncrement = true;
var numberToIncrement = 0;
function increment {
numberToIncrement++;
$('#mySpan').text(numberToIncrement);
if (doIncrement) {
setTimeout(increment, 1000);
}
}
You could use the setInterval function that allows you to run code at some time intervals. clearInterval allows to stop the task.
http://jsfiddle.net/d52Pw/
var $totalEl = $('#total');
$.get('other.html', function(data) {
var len = $('.doc', data).length,
count = 0,
int = setInterval(function () {
if (++count === len) {
//when we reach len, we stop the task
clearInterval(int);
}
$totalEl.html(count);
}, 500); //run every 1/2 sec
});

Optimizing Javascript Loop for Wheel Game

I have a game I'm creating where lights run around the outside of a circle, and you must try and stop the light on the same spot three times in a row. Currently, I'm using the following code to loop through the lights and turn them "on" and "off":
var num_lights = 20;
var loop_speed = 55;
var light_index = 0;
var prevent_stop = false; //If true, prevents user from stopping light
var loop = setTimeout(startLoop, loop_speed);
function startLoop() {
prevent_stop = false;
$(".light:eq(" + light_index + ")").css("background-color", "#fff");
light_index++;
if(light_index >= num_lights) {
light_index = 0;
}
$(".light:eq(" + light_index + ")").css("background-color", "red");
loop = setTimeout(startLoop, loop_speed);
}
function stopLoop() {
clearTimeout(loop);
}
For the most part, the code seems to run pretty well, but if I have a video running simultaneously in another tab, the turning on and off of the lights seems to chug a bit. Any input on how I could possibly speed this up would be great.
For an example of the code from above, check out this page: http://ericditmer.com/wheel
When optimizing the thing to look at first is not doing twice anything you only need to do once. Looking up an element from the DOM can be expensive and you definitely know which elements you want, so why not pre-fetch all of them and void doing that multiple times?
What I mean is that you should
var lights = $('.light');
So that you can later just say
lights.eq(light_index).css("background-color", "red");
Just be sure to do the first thing in a place which keeps lights in scope for the second.
EDIT: Updated per comment.
I would make a global array of your selector references, so they selector doesn't have to be executed every time the function is called. I would also consider swapping class names, rather than attributes.
Here's some information of jQuery performance:
http://www.componenthouse.com/article-19
EDIT: that article id quite old though and jQuery has evolved a lot since. This is more recent: http://blog.dynatrace.com/2009/11/09/101-on-jquery-selector-performance/
You could try storing the light elements in an array instead of using a selector each time. Class selectors can be a little slow.
var elements = $('.light');
function startLoop() {
prevent_stop = false;
$(elements[light_index]).css('background-color', '#fff');
...
}
This assumes that the elements are already in their intended order in the DOM.
One thing I will note is that you have used a setTimeout() and really just engineered it to behave like setInterval().
Try using setInterval() instead. I'm no js engine guru but I would like to think the constant reuse of setTimeout has to have some effect on performance that would not be present using setInterval() (which you only need to set once).
Edit:
Curtousy of Diodeus, a related post to back my statement:
Related Stack Question - setTimeout() vs setInterval()
OK, this includes some "best practice" improvements, if it really optimizes the execution speed should be tested. At least you can proclaim you're now coding ninja style lol
// create a helper function that lend the array reverse function to reverse the
// order of a jquery sets. It's an object by default, not an array, so using it
// directly would fail
$.fn.reverse = Array.prototype.reverse;
var loop,
loop_speed = 55,
prevent_stop = false,
// prefetch a jquery set of all lights and reverses it to keep the right
// order when iterating backwards (small performance optimization)
lights = $('.light').reverse();
// this named function executes as soon as it's initialized
// I wrapped everything into a second function, so the variable prevent_stop is
// only set once at the beginning of the loop
(function startLoop() {
// keep variables always in the scope they are needed
// changed the iteration to count down, because checking for 0 is faster.
var num_lights = light_index = lights.length - 1;
prevent_stop = false;
// This is an auto-executing, self-referencing function
// which avoids the 55ms delay when starting the loop
loop = setInterval((function() {
// work with css-class changing rather than css manipulation
lights.eq( light_index ).removeClass('active');
// if not 0 iterate else set to num_lights
light_index = (light_index)? --light_index:num_lights;
lights.eq( light_index ).addClass('active');
// returns a referenze to this function so it can be executed by setInterval()
return arguments.callee;
})(), loop_speed);
})();
function stopLoop() {
clearInterval(loop);
}
Cheers neutronenstern

Javascript: How to put a simple delay in between execution of javascript code?

I have a for loop which iterates more than 10,000 times in a javascript code. The for loop creates and adds < div > tags into a box in the current page DOM.
for(i = 0; i < data.length; i++)
{
tmpContainer += '<div> '+data[i]+' </div>';
if(i % 50 == 0) { /* some delay function */ }
}
containerObj.innerHTML = tmpContainer;
i want to put a delay after every 50 < div > tags so what will be the code at the place of
/* some delay function */
because its taking too much time to load all 10,000 < div > tags. i want to update the box in chunks of 50 < div > tags.
thanks in advance.
There's a handy trick in these situations: use a setTimeout with 0 milliseconds. This will cause your JavaScript to yield to the browser (so it can perform its rendering, respond to user input and so on), but without forcing it to wait a certain amount of time:
for (i=0;i<data.length;i++) {
tmpContainer += '<div> '+data[i]+' </div>';
if (i % 50 == 0 || i == data.length - 1) {
(function (html) { // Create closure to preserve value of tmpContainer
setTimeout(function () {
// Add to document using html, rather than tmpContainer
}, 0); // 0 milliseconds
})(tmpContainer);
tmpContainer = ""; // "flush" the buffer
}
}
Note: T.J. Crowder correctly mentions below that the above code will create unnecessary functions in each iteration of the loop (one to set up the closure, and another as an argument to setTimeout). This is unlikely to be an issue, but if you wish, you can check out his alternative which only creates the closure function once.
A word of warning: even though the above code will provide a more pleasant rendering experience, having 10000 tags on a page is not advisable. Every other DOM manipulation will be slower after this because there are many more elements to traverse through, and a much more expensive reflow calculation for any changes to layout.
You could use the window.setTimeout function to delay the execution of some code:
if(i % 50 == 0) {
window.setTimeout(function() {
// this will execute 1 second later
}, 1000);
}
But your javascript will continue to execute. It won't stop.
I'd break out the code creating the divs into a function, and then schedule execution of that function periodically via setTimeout, like this:
function createThousands(data) {
var index;
index = 0;
doAChunk();
function doAChunk() {
var counter;
for (counter = 50; counter > 0; --counter) {
// Are we done?
if (index >= data.length) {
// Yup
return;
}
// ...create a div...
// Move to the next
++index;
}
// Schedule the next pass
setTimeout(doAChunk, 0); // 0 = defer to the browser but come back ASAP
}
}
This uses a single closure, doAChunk to do the work. That closure is eligible for garbage collection once its work is done. (More: Closures are not complicated)
Live example
it takes much time because the reflows. you should create a document fragment and then adding the brats.
When does reflow happen in a DOM environment?
Javascript Performance - Dom Reflow - Google Article
sleeping will not solve your problem
on the other hand, you creating a string containing the innerhtml and the add to innerhtml. the string stuff really dont needs a big performance, but when you execute the .innerhtml command, it starts a process, which parse your string and creating elements and appending them. you cant interrupt or add a delay.
the innerhtml process cannot be sleeped or interrupted.
you need to generate the elements one by one, and after 50 elemnts added, create a settimeout delay.
var frag = document.createDocumentFragment();
function addelements() {
var e;
for(i=0;i<50;++i) {
e = document.createElement('div');
frag.appendChild(e);
}
dest.appendChild(frag);
window.setTimeout(addelements,1000);
}
Here is the real trick to put a delay in javascript without hanging the browser.
You need to use a ajax function with synchronous method which will call a php page and in that php page you can use the sleep() php function !
http://www.hklabs.org/articles/put-delay-in-javascript

Categories

Resources