This question already has answers here:
Running a long operation in javascript?
(5 answers)
Closed 9 years ago.
I created a brute-force like script which basically needs to check more than 27,000 options, and after each check displays the result inside a div.
The script is coded correctly and if I lower the number of options it works sufficiently well, but if I have many options, after a few seconds, a window pops up telling me that my script is unresponsive. How can I make it responsive while checking this many options.
Oh and I almost forgot, it displays data (which is displayed after every check) only when that pop-up window appears (kinda weird).
Asynchronous batch processing may solve your problem:
var options = ...; // your code
// I assume you are using something like this
function processAll() {
for(var i=0; i<options.length; ++i) ... // causes unresponsivity
}
// try to use this instead
function batchProcessing(from) {
if(from >= options.length) return;
var to = Math.min(1000, options.length-from);
for(var i=from; i<from+to; ++i) ... // your code
// run the next batch asynchronously, let the browser catch the breath
setTimeout(batchProcessing.bind(null, from+1000));
}
Related
This question already has answers here:
JavaScript closure inside loops – simple practical example
(44 answers)
Closed 6 years ago.
I'm pretty new to Javascript and am writing a Chrome extension which is offsetting the "click" function call in a list of emails on an emailing website. My problem is that the functions that I'm offsetting are being passed by reference, which is causing them to eventually all point to a single click event.
My code:
//Offsets the email links
for (var i = emails.length - 1; i >= amountToRemove; i--) {
var a = emails[i];
var b = emails[i - amountToRemove];
a.addEventListener('click', (x) => {b.click(); x.stopPropagation(); return false;})
}
And a little elaboration of the problem:
ie. say amountToRemove is 2 and i = 10; the first two "shifts" of click() events go well, but in the 3rd shift, the click() function for the 9th (last) and 7th objects in the array point to the same function, which is not what was intended.
I've tried making a "deep copy" of the function using
JSON.parse(JSON.stringify(object))
as well as other methods recommended by the internet to no avail.
My question is, is there a way of making a "deep" copy of the .click() functions so that they don't point to the same object and cause each click event to eventually point to the same single click event?
It seems like your problem is not about needing a "deep" copy of functions. Instead, I think your issue is that when you click on the 9th email, it triggers click on the 7th email, which then triggers click on the 5th email. A slightly hacky way to do what you want would be:
//Offsets the email links
var interceptingClick = false;
for (var i = emails.length - 1; i >= amountToRemove; i--) {
var a = emails[i];
var b = emails[i - amountToRemove];
a.addEventListener('click', (x) => {
if (interceptingClick)
return;
interceptingClick = true;
b.click();
interceptingClick = false;
x.stopPropagation();
return false;
});
}
But the behavior you're trying to achieve seems a bit strange. Maybe you could tell us why you want to do that in case there's something else you could do that's easier and more robust.
Brace yourself for a confusing explanation. I'm making a stupid little quiz site. Five question on the page. Answers stored in an xml file. Questions are displayed to players one at at time with an input for them to answer. When the player makes a guess for a particular answer, JQuery posts the guess to a php file that checks the answer against what is stored in the xml, and returns either 1 (correct) and or 0 (wrong). The post is triggered by the text input's keyup event.
I have a global array called remaining that stores the questions on the page that are still to be answered. This is populated on page load with the values [1, 2, 3, 4, 5]. As questions are answered correctly, the appropriate number is removed. So if the player answers question one, remaining will contain [2, 3, 4, 5].
var current;
var remaining;
$(document).ready(function() {
// What question the player is on.
current = 1;
// Questions unanswered.
remaining = new Array(1, 2, 3, 4, 5);
$('#in').keyup(function() {
// Send answer to server to check if it's right.
CheckGuess($(this).val());
});
});
function CheckGuess(guess) {
if (guess.length > 2 && guess.length < 100)
{
$.post(
"class/check.php",
"current=" + current + "&answer=" + guess,
function(check) {
if (check == 1) {
AnswerCorrect();
}
},
"json"
);
}
}
function AnswerCorrect() {
// User guessed correctly.
if (remaining.length != 1) {
var next;
// Remove complete question from array of remaining values.
for (var i = 0; i < remaining.length; i++ ) {
if (remaining[i] == current) {
// Set the next question. It will be the next one in the array
// or the previous if the current question is the last in the array.
if (i != (remaining.length - 1)) {
next = remaining[i + 1];
} else {
next = remaining[i - 1];
}
debugger;
// Remove current question.
remaining.splice(i, 1);
// Get out of the for loop.
break;
}
}
// Set current as next.
current = next;
// Set the href for the next question.
var destination = "#m" + next;
// Scroll to next question.
$.scrollTo($(destination), { duration: 1000});
// Clear input box.
$("#in").val("");
}
else {
// Quiz complete.
}
}
It all works to some extent. But I'm having horrible trouble with some mysterious issue. I've stepped through it with FireBug and what seems to happen is:
(a). Page loads. I can see remaining has the values [1,2,3,4,5] as I want.
(b). Player enters correct answer for question one. "1" is then removed from remaining so it has values [2,3,4,5] as expected.
(c). Player enters correct answer for question two, but now as soon as FireBug hits any breakpoint I have set I can see that remaining has the values [3,4,5]. So before the function AnswerCorrect() is called 2 is gone. Where did 2 go?! Then when AnswerCorrect() does actually run it thinks the player is on question 3 (because remaining contains [3,4,5]). The overall result is that when the player answers question 2, both 2 and 3 are marked as correct.
I hope my explanation was somewhat clear. I've never understood anything less in my life. I don't understand what happens between point (b) and (c) above. I'm stepping through the code and I can't find where remaining drops "2". FireBug is letting me down. It doesn't seem to be breaking somewhere when it should. If I have a breakpoint on the split() I don't see the removal of 2 could be going on without me seeing it. Anyone have any clue? I'd really appreciate any help before I go mad.
EDIT - More Info
(Sorry for the slow reply, I was at work all day).
The real issue is that I can't see where the 2 is being dropped. One thing I was thinking that may be the problem (but I don't know enough about Javascript to know if this is possible):
The jQuery post fires frequently; every time the player enters a letter. Is it possible that AnswerCorrect() could be called multiple times simultaneously? So that two or more "instances" of AnswerCorrect() are running concurrently?
EDIT 2
I decided to give up on this method. I can't figure out the problem. I rewrote it so that now the post occurs on document load and the answers are stored server-side in an array. That's probably a better way of doing it anyway, since I only have one post to the server instead of many. And it all works fine now. Consider this thread solved.
I think you're heavily overcomplicating things. There is no need for a loop or a splice, and in your looping you're mixing array index with array value in attempting to figure out where you are. It is very very confusing--even if you had it figured out, the next guy to come along and maintain it would be lost (even if that's you 6 months from now).
You want the first item off the array, and you want the array length modified accordingly. For this you should use Array.prototype.shift(). I've rearranged some of your code for better practices, but tried to leave some of it in the same layout so as to avoid making it unrecognizable to you:
var remaining = [ 1, 2, 3, 4, 5 ];
$( function()
{
$( '#in' ).keyup( function()
{
// Send answer to server to check if it's right.
CheckGuess( $( this ).val() );
} );
Next();
} );
function CheckGuess( guess )
{
if( guess.length > 2 && guess.length < 100 )
{
$.post(
'class/check.php',
{
current: + current,
answer + guess
},
function( check )
{
if( check == 1 )
{
Next();
}
},
'json'
);
}
}
function Next()
{
// User guessed correctly.
if( remaining.length )
{
current = remaining.shift();
// Scroll to next question.
$.scrollTo( $( '#m' + current ), { duration: 1000 } );
// Clear input box.
$( '#in' ).val( '' );
}
else
{
// Quiz complete.
}
}
I don't honestly see any logic errors in the code you have posted. I put it in a jsFiddle here with a simulated async check for correct and a view of the remaining answers every time. It seems to work for me. So, if your real code is having this issue you're talking about, then there must be something else to the code that you haven't shown us here.
One typical place that things can go wrong in an app with an asynchronous ajax call like this and some global state is that the async part might not be done right. It has to wait for the success handler of the server call before changing any state. If state is changed before that processes, then it may mess things up when it finishes.
What the other two answers have suggested are simpler ways to write the code if you can assume that the first question in the array is always the one being worked on. Your AnswerCorrect() code does not assume that the only answer being worked on is the first on in the array and is thus more general so if that is the case, then I don't think you can use the code from those other answers.
So, I think the answer to your question is that there must be more to what is causing the error than you have disclosed in your question because I don't see any serious logic error with what you have posted and the "simulated" jsFiddle that uses your code seems to work. So, what else is there in your app that you haven't shown us here?
EDIT:
The most common mistake when first using ajax calls is related to the asynchronous nature of the ajax call. When you make the call, it STARTS the ajax call and it happens in the background while the rest of your code keeps running. It does not finish until sometime later when the completion/success function is invoked. If you forget that fact and you do other things after you STARTED the ajax call BEFORE it has completed, then your logic will go awry. The sample you've posted here doesn't have that problem, but it would be my guess that there is more to your real code after the POST to the server and perhaps that is the source of the error/confusion. Since you are using some global variables to keep track of state, if you touch any of those global variables after the POST call and before the completion function is called (like to move to the next question), it will mess up your logic. The success function will come through and your global state will have been incorrectly altered. That's just a guess because all I have to go by is the code you've posted which doesn't have the problem you are experiencing.
If you want to post the real code or give us a link to the real page, we can take a look there.
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'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)
We are using Bing and/or Google javascript map controls, sometimes with large numbers of dynamically alterable overlays.
I have read http://support.microsoft.com/kb/175500/en-us and know how to set the MaxScriptStatments registry key.
Problem is we do not want to programmatically set this or any other registry key on users' computers but would rather achieve the same effect some other way.
Is there another way?
Hardly anything you can do besides making your script "lighter". Try to profile it and figure out where the heaviest crunching takes place, then try to optimize those parts, break them down into smaller components, call the next component with a timeout after the previous one has finished and so on. Basically, give the control back to the browser every once in a while, don't crunch everything in one function call.
Generally a long running script is encountered in code that is looping.
If you're having to loop over a large collection of data and it can be done asynchronously--akin to another thread then move the processing to a webworker(http://www.w3schools.com/HTML/html5_webworkers.asp).
If you cannot or do not want to use a webworker then you can find your main loop that is causing the long running script and you can give it a max number of loops and then cause it to yield back to the client using setTimeout.
Bad: (thingToProcess may be too large, resulting in a long running script)
function Process(thingToProcess){
var i;
for(i=0; i < thingToProcess.length; i++){
//process here
}
}
Good: (only allows 100 iterations before yielding back)
function Process(thingToProcess, start){
var i;
if(!start) start = 0;
for(i=start; i < thingToProcess.length && i - start < 100; i++){
//process here
}
if(i < thingToProcess.length) //still more to process
setTimeout(function(){Process(thingToProcess, i);}, 0);
}
Both can be called in the same way:
Process(myCollectionToProcess);