I am wondering how I can solve a hanging page with JS.
I have a JS loop which I'm testing like this, but it seems to hang forever - is there a way to stop it hanging while still completing the script ?:
<div id="my_data"></div>
<script>
function test(value){
output= [];
do{
value++;
output.push(value);
document.getElementById('my_data').innerHTML = (output.join(''));
}while(value < 10000000);
alert('end'); // never occurs
}
test(0);
</script>
You're updating the DOM with an increasingly long string ten million times.
If you're some sort of super-being from the future, then maybe this makes sense.
Javascript is single-threaded, so nothing else can happen while it's running your loop. It's running ten million times, and on each run it's setting the innerHTML of #my_data to something new. It's very inefficient and seems useless. What are you trying to accomplish?
You are concatenating 10,000,000 consecutive numbers together into a string, which really will not work well on any modern supercomputer.
If you'd like to poll the progress, setup a timer to do it somewhat slower. It isn't practical, but it looks nice: http://jsfiddle.net/V6jjT/2/
HTML:
<div id="my_data"></div>
Script:
var value = 0;
var interval = setInterval(function() {
if (value > 100) {
clearInterval(interval);
return;
}
value++;
document.getElementById('my_data').innerHTML += ' ' + value;
}, 10);
Running a tight loop like that will not update anything until it's done; besides, 10M array items?!
If you really want to do this and not let the browser hang until forever you have to use setInterval to allow the browser to refresh in between.
function updater(value, max, interval) {
var output = [],
node = document.getElementById('my_data'),
tid = setInterval(function() {
if (value++ < max) {
output.push(value);
node.innerHTML = (output.join(''));
} else {
alert('done');
clearInterval(tid);
}
}, interval);
}
updater(0, 10, 200);
You should not update the HTML each iteration.
function test(value){
output= [];
do {
value++;
output.push(value);
} while(value < 10000000);
document.getElementById('my_data').innerHTML = (output.join(''));
alert('end'); // never occurs
}
should work (although 10 million numbers in a HTML will still need their time to render).
If you want to see numbers running, you should have a look at window.setInterval - the browser needs some time between js execution to refresh its view. So, you will have to rewrite your code running several chunks of data asynchrously. See also:
Running a long operation in javascript? (example code for your loop)
How can I force the browser to redraw while my script is doing some heavy processing?
Javascript async loop processing
Prevent long running javascript from locking up browser
DOM refresh on long running function
Best way to iterate over an array without blocking the UI
Javascript - how to avoid blocking the browser while doing heavy work?
Tight loop is likely to always exhaust computing power. I would do it in chunks. Say, divide your list into smaller lists and pause 5ms in between lists. I can provide an example if you need.
Related
So I'm looking for some advice on the best method for toggling the class (set of three) of an element in a loop ending at 360 iterations. I'm trying to avoid nested loops, and ensure good performance.
What I have:
// jQuery flavour js
// vars
var framesCount = '360'; // total frames
var framesInterval = '5000'; // interval
var statesCount = 3; // number of states
var statesCountSplit = framesInterval/statesCount;
var $scene = $('#scene');
var $counter = $scene.find('.counter');
// An early brain dump
for (f = 1; f < framesCount; f += 1) {
$counter.text(f);
for (i = 1; i < statesCount; i += 1) {
setTimeout(function() {
$scene.removeClass().addClass('state-'+i);
}, statesCountSplit);
}
}
So you see for each of 360 frames there are three class switchouts at intervals. Although I haven't tested I'm concerned for the performance hit here once that frames value goes into the thousands (which it might).
This snippet is obviously flawed (very), please let me know what I can do to make this a) work, b) work efficiently. Thanks :-)
Some general advice:
1) Don't declare functions in a loop
Does this really need to be done in a setTimeout?
for (i = 1; i < statesCount; i += 1) {
setTimeout(function() {
$scene.removeClass().addClass('state-'+i);
}, statesCountSplit);
}
2) DOM operations are expensive
Is this really necessary? This will toggle so fast that you won't notice the counter going up. I don't understand the intent here, but it seems unnecessary.
$counter.text(f);
3) Don't optimize early
In your question, you stated that you haven't profiled the code in question. Currently, there's only about 1000 iterations, which shouldn't be that bad. DOM operations aren't too bad, as long as you aren't inserting/removing elements, and you're just modifying them.
I really wouldn't worry about performance at this point. There are other micro-optimizations you could apply (like changing the for loop into a decrementing while loop to save on a compare), but you gave no indication that the performance is a problem.
Closing thoughts
If I understand the logic correctly, you're code doesn't match it. The code will currently increment .counter as fast as the processor can iterate over your loops (should only take a few milliseconds or so for everything) and each of your "class switchouts" will fire 360 times within a few milliseconds of each other.
Fix your logic errors first, then worry about optimization if it becomes a problem.
Don't use a for loop for this. This will generate lots of setTimeout events which is known to slow browsers down. Use a single setTimeout instead:
function animate(framesCount, statesCount) {
$scene.removeClass().addClass('state-'+statesCount);
if (framesCount) {
setTimeout(
function(){
animate(framesCount-1,(statesCount%3)+1);
},
statesCountSplit
);
}
}
animate(360*3,1);
I have wrote this code to make seconds (with decisec & centisec) counting up.
You've wasted time <span id="alltime">0.00</span> seconds.
<script type="text/javascript">
function zeroPad(num,count)
{
var numZeropad = num + '';
while(numZeropad.length < count) { numZeropad = "0" + numZeropad; }
return numZeropad; }
function counttwo() {
tall = document.getElementById('alltime').innerHTML;
if(parseFloat(tall) < 1.00) { tnew2 = tall.replace('0.0','').replace('0.',''); }
else { tnew2 = tall.replace('.',''); }
tnum = parseInt(tnew2) + 1;
//check if have to add zero
if(tnum >= 100) { tstr1 = tnum + ''; }
else { tstr1 = zeroPad(tnum,3); }
tlast = tstr1.substr(0,tstr1.length - 2) + '.' + tstr1.substr(tstr1.length - 2);
document.getElementById("alltime").innerHTML = tlast;
}
var inttwo=setInterval("counttwo()",10);
</script>
In HTML document and run.
It works well but when I use Firefox 4 and run the code. Seems like it LAG a bit (stop a bit before counting up) when it's on some numbers (randomly like 12.20, 4.43). I've tried change "counttwo()" to counttwo but that doesn't help.
I have told some of my friends to run on Firefox 4 too. They said it doesn't lag at all. This cause because of my computer ? or My Firefox ? or something else ?
Thanks in advance!
PS. Fiddle here: http://jsfiddle.net/XvkGy/5/ Mirror: http://bit.ly/hjVtXS
When you use setInterval or setTimeout the time interval is not exact for several reasons. It is dependent on other javascript running, the browser, the processor etc. You have to take a reliability of +- 15ms for granted afaik. See also ...
That's a lot of counting, so on some computer, yes, it might lag (if it's a prehistoric one or the user's got his processor really busy with something), also if I'm right, that thing won't work with Chrome's V8, since that script would freeze if you switched tabs, and resume executing only when you return to that tab.
If you're just seeing pauses every so often, you're probably seeing garbage collection or cycle collection pauses.
You can test this by toggling your javascript.options.mem.log preference to true in about:config and then watching the error console's "Messages" tab as your script runs. If the GC/CC messages are correlated with your pauses, then they're the explanation for what you see.
As for why you see it but others don't... do you see the problem if you disable all your extensions?
The problem with setInterval is that it can eventually lead to a back-up. This happens because the JavaScript engine tries to execute the function on the interval (in your case, 10ms), but if ever that execution takes longer than 10ms, the JS engine starts trying to execute the next interval before the current one stops (which really just means it queues it up to run as soon as the previous callback finishes).
Since JavaScript executes single-threaded (with the exception of web workers in HTML 5), this can lead to pauses in your UI or DOM updates because it is continuously processing JavaScript callbacks from your setInterval. In worst case scenarios, the whole page can become permanently unresponsive because your stack of uncompleted setInterval executions gets longer and longer, never fully finishing.
With a few exceptions, it is generally considered a safer bet to use setTimeout (and invoking the setTimeout again after execution of the callback) instead of setInterval. With setTimeout, you can ensure that one and only one timeout is ever queued up. And since the timers are only approximate anyway (just because you specify 10ms doesn't mean it will happen at exactly 10ms), you don't typically gain anything from using setInterval over setTimeout.
An example using setTimeout:
var count = function(){
// do something
// queue up execution once again
setTimeout(count, 10);
};
count();
One reason why you may see pauses on some browsers, and not others, is because not all JavaScript engines are created equal :). Some are faster than others, and as such, less likely to end up with a setInterval backup.
Different browsers use different JavaScript engines, so it's possible that this code just finds a spot where Firefox's JägerMonkey scripting engine has some problems. There doesn't seem to be any obvious inefficiencies in the counting itself..
If it's working on your friends' installs of FF4, then it's probably just an isolated problem for you, and there isn't much you'll be able to do by changing the code.
I have a JavaScript function that contains a for loop that iterates so many times.
After calling this function, the IE browser displays this message:
Stop running this script?
A script on this page is causing your web browser to run slowly.
If it continues to run, your computer might become unresponsive.
How can I fix this?
is there anyway I can disable this message from IE?
This message displays when Internet Explorer reaches the maximum number of synchronous instructions for a piece of JavaScript. The default maximum is 5,000,000 instructions, you can increase this number on a single machine by editing the registry.
Internet Explorer now tracks the total number of executed script statements and resets the value each time that a new script execution is started, such as from a timeout or from an event handler, for the current page with the script engine. Internet Explorer displays a "long-running script" dialog box when that value is over a threshold amount.
The only way to solve the problem for all users that might be viewing your page is to break up the number of iterations your loop performs using timers, or refactor your code so that it doesn't need to process as many instructions.
Breaking up a loop with timers is relatively straightforward:
var i=0;
(function () {
for (; i < 6000000; i++) {
/*
Normal processing here
*/
// Every 100,000 iterations, take a break
if ( i > 0 && i % 100000 == 0) {
// Manually increment `i` because we break
i++;
// Set a timer for the next iteration
window.setTimeout(arguments.callee);
break;
}
}
})();
The unresponsive script dialog box shows when some javascript thread takes too long too complete. Editing the registry could work, but you would have to do it on all client machines. You could use a "recursive closure" as follows to alleviate the problem. It's just a coding structure in which allows you to take a long running for loop and change it into something that does some work, and keeps track where it left off, yielding to the browser, then continuing where it left off until we are done.
Figure 1, Add this Utility Class RepeatingOperation to your javascript file. You will not need to change this code:
RepeatingOperation = function(op, yieldEveryIteration) {
//keeps count of how many times we have run heavytask()
//before we need to temporally check back with the browser.
var count = 0;
this.step = function() {
//Each time we run heavytask(), increment the count. When count
//is bigger than the yieldEveryIteration limit, pass control back
//to browser and instruct the browser to immediately call op() so
//we can pick up where we left off. Repeat until we are done.
if (++count >= yieldEveryIteration) {
count = 0;
//pass control back to the browser, and in 1 millisecond,
//have the browser call the op() function.
setTimeout(function() { op(); }, 1, [])
//The following return statement halts this thread, it gives
//the browser a sigh of relief, your long-running javascript
//loop has ended (even though technically we havn't yet).
//The browser decides there is no need to alarm the user of
//an unresponsive javascript process.
return;
}
op();
};
};
Figure 2, The following code represents your code that is causing the 'stop running this script' dialog because it takes so long to complete:
process10000HeavyTasks = function() {
var len = 10000;
for (var i = len - 1; i >= 0; i--) {
heavytask(); //heavytask() can be run about 20 times before
//an 'unresponsive script' dialog appears.
//If heavytask() is run more than 20 times in one
//javascript thread, the browser informs the user that
//an unresponsive script needs to be dealt with.
//This is where we need to terminate this long running
//thread, instruct the browser not to panic on an unresponsive
//script, and tell it to call us right back to pick up
//where we left off.
}
}
Figure 3. The following code is the fix for the problematic code in Figure 2. Notice the for loop is replaced with a recursive closure which passes control back to the browser every 10 iterations of heavytask()
process10000HeavyTasks = function() {
var global_i = 10000; //initialize your 'for loop stepper' (i) here.
var repeater = new this.RepeatingOperation(function() {
heavytask();
if (--global_i >= 0){ //Your for loop conditional goes here.
repeater.step(); //while we still have items to process,
//run the next iteration of the loop.
}
else {
alert("we are done"); //when this line runs, the for loop is complete.
}
}, 10); //10 means process 10 heavytask(), then
//yield back to the browser, and have the
//browser call us right back.
repeater.step(); //this command kicks off the recursive closure.
};
Adapted from this source:
http://www.picnet.com.au/blogs/Guido/post/2010/03/04/How-to-prevent-Stop-running-this-script-message-in-browsers
In my case, while playing video, I needed to call a function everytime currentTime of video updates. So I used timeupdate event of video and I came to know that it was fired at least 4 times a second (depends on the browser you use, see this). So I changed it to call a function every second like this:
var currentIntTime = 0;
var someFunction = function() {
currentIntTime++;
// Do something here
}
vidEl.on('timeupdate', function(){
if(parseInt(vidEl.currentTime) > currentIntTime) {
someFunction();
}
});
This reduces calls to someFunc by at least 1/3 and it may help your browser to behave normally. It did for me !!!
I can't comment on the previous answers since I haven't tried them. However I know the following strategy works for me. It is a bit less elegant but gets the job done. It also doesn't require breaking code into chunks like some other approaches seem to do. In my case, that was not an option, because my code had recursive calls to the logic that was being looped; i.e., there was no practical way to just hop out of the loop, then be able to resume in some way by using global vars to preserve current state since those globals could be changed by references to them in a subsequent recursed call. So I needed a straight-forward way that would not offer a chance for the code to compromise the data state integrity.
Assuming the "stop script?" dialog is coming up during a for() loop executuion after a number of iterations (in my case, about 8-10), and messing with the registry is no option, here was the fix (for me, anyway):
var anarray = [];
var array_member = null;
var counter = 0; // Could also be initialized to the max desired value you want, if
// planning on counting downward.
function func_a()
{
// some code
// optionally, set 'counter' to some desired value.
...
anarray = { populate array with objects to be processed that would have been
processed by a for() }
// 'anarry' is going to be reduced in size iteratively. Therefore, if you need
// to maintain an orig. copy of it, create one, something like 'anarraycopy'.
// If you need only a shallow copy, use 'anarraycopy = anarray.slice(0);'
// A deep copy, depending on what kind of objects you have in the array, may be
// necessary. The strategy for a deep copy will vary and is not discussed here.
// If you need merely to record the array's orig. size, set a local or
// global var equal to 'anarray.length;', depending on your needs.
// - or -
// plan to use 'counter' as if it was 'i' in a for(), as in
// for(i=0; i < x; i++ {...}
...
// Using 50 for example only. Could be 100, etc. Good practice is to pick something
// other than 0 due to Javascript engine processing; a 0 value is all but useless
// since it takes time for Javascript to do anything. 50 seems to be good value to
// use. It could be though that what value to use does depend on how much time it
// takes the code in func_c() to execute, so some profiling and knowing what the
// most likely deployed user base is going to be using might help. At the same
// time, this may make no difference. Not entirely sure myself. Also,
// using "'func_b()'" instead of just "func_b()" is critical. I've found that the
// callback will not occur unless you have the function in single-quotes.
setTimeout('func_b()', 50);
// No more code after this. function func_a() is now done. It's important not to
// put any more code in after this point since setTimeout() does not act like
// Thread.sleep() in Java. Processing just continues, and that is the problem
// you're trying to get around.
} // func_a()
function func_b()
{
if( anarray.length == 0 )
{
// possibly do something here, relevant to your purposes
return;
}
// -or-
if( counter == x ) // 'x' is some value you want to go to. It'll likely either
// be 0 (when counting down) or the max desired value you
// have for x if counting upward.
{
// possibly do something here, relevant to your purposes
return;
}
array_member = anarray[0];
anarray.splice(0,1); // Reduces 'anarray' by one member, the one at anarray[0].
// The one that was at anarray[1] is now at
// anarray[0] so will be used at the next iteration of func_b().
func_c();
setTimeout('func_b()', 50);
} // func_b()
function func_c()
{
counter++; // If not using 'anarray'. Possibly you would use
// 'counter--' if you set 'counter' to the highest value
// desired and are working your way backwards.
// Here is where you have the code that would have been executed
// in the for() loop. Breaking out of it or doing a 'continue'
// equivalent can be done with using 'return;' or canceling
// processing entirely can be done by setting a global var
// to indicate the process is cancelled, then doing a 'return;', as in
// 'bCancelOut = true; return;'. Then in func_b() you would be evaluating
// bCancelOut at the top to see if it was true. If so, you'd just exit from
// func_b() with a 'return;'
} // func_c()
I'm trying to write a simple music-sequencer in Javascript.
Sounds will be played by SoundManager2
I quickly realised that setTimeout and setInterval are useless for this kind of timing. Their accuracy is simply not good enough.
So what I am trying instead is this:
Create a queue of sound-events (a 2d array [ note, time ] )
Process the queue in a while loop
In pseudo code it could look like this:
// The queue of time/note values (millisecs)
var q = [[0, C], [250, D], [500, E]]
var begin = (new Date).getTime()
while(q.length > 0 ){
var now = (new Date).getTime()
var eventTime = q[0][0] + begin
if( now >= eventTime){
playNote(q[0][1]) // Play the note
q.shift() // Now that the note has been played, discard it.
}
}
Through debugging I've found that this method seems precise enough (the playNote call is made at the exact time it's supposed to).
However while the 'sequence' is playing, all other Javascript (including the bit that actually MAKES the sound) is suspended which is hardly a surprise.
This means that I have silence for as long time it takes to run the sequence and then ALL of the calls to playNote are executed.
I've tried isolating the while-loop in it's own function and then call it through setTimeout, in the hopes that this would create a background thread executing it's own thing (namely playing the sequence) without halting all other JS execution. That doesn't work
I've also tried calling the playNote function using setTimeout, in the hopes that while indeed the UI is frozen at least the sequence is playing correctly, but that doesn't work either.
I've also tried a combination, but as you probably already guessed, that doesn't work either.
So my question is:
How can I have a queue of events processed with precise timing, while NOT shutting down all other code-execution during the run
I don't know if there is a solution for older browers, but web workers are intented for doing parrallel execution in JavaScript.
They are supported in recent versions of Chrome and Firefox, I don't know about other browsers.
If you replace your while loop with a recursive function (it doesn't have to be self-executing) the browser wont freeze.
(function foo (q) {
var now = (new Date).getTime(),
eventTime = q[0][0] + begin;
if (q.length > 0) {
if (now >= eventTime) {
playNote(q[0][1]); // Play the note
q.shift(); // Discard played note
}
return foo(q); // Pass q into the next round
} else {
return; // End the "loop"
}
}(q));
Edit: The problem with recursive calls + timer accuracy should be taken care of by setInterval:
var timer = setInterval(function foo () {
var now = (new Date).getTime(),
eventTime = q[0][0] + begin;
if (q.length > 0) {
if (now >= eventTime) {
playNote(q[0][1]); // Play the note
q.shift(); // Discard played note
}
} else {
clearInterval(timer); // End the "loop"
}
}, 10); // This is in ms (10 is just a random number)
I'd like to run some calculations in a browser window, but I don't want it to slow the client computer down for user interaction, especially for single core machines. Is there some way to adjust the nice level of my executing JavaScript so that it will execute as fast as possible without detracting from the responsiveness of the machine?
I can't think of anything else than delaying execution of your calculations. In example, dividing all work into small pieces and then running them sequentially with some delay (using setTimeout or setInterval) between each task.
Create open loops... an example
This is a close loop
for( i = 0 ; i < 10000 ; i++)
{
doSomeMath(i);
}
This is an open loop
i = 0;
iMax = 10000
var MyWorkingThread =
setInterval(function()
{
if( i < iMax)
{
doSomeMath(i);
i++;
}
else
{
clearInterval(MyWorkingThread);
}
},1);
You can make a more, or a less work inside the open loop, but this is the general idea. I have made this many times for similar issues and I left the browser work very smooth.
For speed attempt to use a single multidimensional array to contain your data and then at the end of your function use a single join to convert that array to a string for output and output that data using the innerHTML method. That is the fastest possible method of containing and serving data in JavaScript. Definitely do not use DOM methods or elements to output your data as that is about 4 times as slow.
Output your data as few times as possible. This is going to be determined upon which event you use to execute your function. I recommend not using the onload event as this will slow the initial load time of your page. I would recommend using the onclick function associated with a button, because then the user is aware that they caused the execution that is slowing down your page.
I did some testing, and the browser needs quite some time between bursts of work to be reasonably responsive:
function work(cnt) {
// do some heavy work
for (var i=0;i<100000000;i++) ;
// start next work
if (cnt > 0) {
window.setTimeout(function(){work(cnt-1);},200);
}
}
run the calculation on the server with an ajax request
open a new window or frame and run the code there
run the code in a loop broken into intervals
be ready to use web-worker processes (html5 asynchronous script)