49

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?

Kara
  • 6,115
  • 16
  • 50
  • 57
scatman
  • 14,109
  • 22
  • 70
  • 93

4 Answers4

80

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;
        }
    }
})();
Andy Gaskell
  • 31,495
  • 6
  • 74
  • 83
Andy E
  • 338,112
  • 86
  • 474
  • 445
  • 4
    Thank you very much for linking to the registry hack. I've been trying run a couple of performance tests on a VM and that IE popup dialog was ruining the results. I had a hell of a time finding this question, but hopefully by adding this comment and mentioning a couple of keywords (like "Stop running this script?" and "IE popup dialog") it'll show up higher in google. – Xavi Jan 16 '11 at 16:28
  • 2
    if you don't give setTimeout an explicit time, ie. skip the second argument, this script will run faster and you'll get the same effect. Not providing a second argument tells the browser to fire the passed function "as soon as possible" rather than "after at least this much time". – David Meister Dec 16 '12 at 11:55
  • 1
    @David: most browsers have a minimum delay for setTimeout and setInterval, usually between 10-20ms, so you wouldn't notice a difference. – Andy E Dec 16 '12 at 12:39
  • @AndyE: I made a simple fiddle here http://jsfiddle.net/Dssj8/ to profile a loop with no timeout, a timeout of 0ms and a timeout of 10ms. On my macbook running firefox, for 1000 iterations of a simple loop, I get 1ms for just running the loop, about 4700ms for a timeout of 0ms and 10500ms for a timeout of 10ms. To me that implies that firefox has a minimum timeout of around 4ms and I kind of doubt that using a faster computer wouldn't lower that further. Running the same tests in Chrome yields almost identical results. setTimeout is clearly slow, so why make it even slower for no reason? – David Meister Dec 17 '12 at 13:35
  • @DavidMeister: I guess the minimum delays have been updated since last I checked. Rather than setting it to 0, removing the argument altogether should have the same effect. Just for you, updated :-P – Andy E Dec 17 '12 at 13:38
  • 2
    @AndyE - I generally don't include the argument, but I see that jQuery swaps between using 0 and 1ms delays for some reason that I don't understand (likely backwards compatibility with something) but it's never ommitted so I mimic that when I'm giving advice. I do know that a few years back jQuery had a hard-coded timeout of 13ms for animation frames that was a bit of a pain to work around. Maybe this lower limit is what you're thinking of? or maybe computers were just slower in the past. – David Meister Dec 17 '12 at 13:42
  • 1
    @DavidMeister: I specifically remember Firefox having a limit of 10ms and Internet Explorer's being even higher. If you check the MDN documentation for [setTimeout and setInterval](https://developer.mozilla.org/en-US/docs/DOM/window.setTimeout#Minimum_delay_and_timeout_nesting) it is confirmed that the minimum used to be 10ms, but is now 4ms as defined by the HTML5 specification and consistent in browsers since 2010. Good to know I'm only 2 years out of date :-) – Andy E Dec 17 '12 at 14:11
  • @AndyE - I take back what I said. Omitting the second argument sent to setTimeout() *does* make the timeout 60% faster but you get spinny wheel (at least in firefox) for almost the entire duration of the script, whereas the 10ms delay is enough to stave off spinny wheel for a good 10 seconds or more, giving the appearance of a much more responsive site even if it's technically a few milliseconds slower. – David Meister Dec 18 '12 at 15:07
  • 1
    @David: interesting... then again it wouldn't be web programming if there weren't a zillion weird quirks here and there :-) – Andy E Dec 18 '12 at 18:57
  • 3
    @AndyE - I did some more experimenting over the Christmas holidays and put together a bit of a write-up on this topic that's a little more in-depth than could be posted straight to Stack Overflow. Thought you might be interested, http://thedavidmeister.info/post/techniques-help-manage-long-running-scripts-and-avoid-timeout-errors – David Meister Jan 13 '13 at 11:42
  • @DavidMeister `"as soon as possible"` is different than `"right now"`. Good point!!! Not providing a second argument tells the browser to fire the passed function "as soon as possible" – LCJ Jul 17 '14 at 19:47
  • This approach didn't work for me as mentioned in http://stackoverflow.com/questions/24847655/settimeout-for-loading-items-in-a-dropdown-list... Any thoughts on this? – LCJ Jul 20 '14 at 05:30
  • 2
    @DavidMeister, that link is dead – Sam Hasler May 09 '16 at 10:20
13

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

Eric Leschinski
  • 146,994
  • 96
  • 417
  • 335
  • 4
    I would argue that you don't want to do this as RepeatingOperation could be called just 1000 times and lead to a minimum 4-10 second period where the site is unresponsive thanks to the hardcoded minium delay on setTimeout(). See the comment thread on Andy E's answer. – David Meister Dec 18 '12 at 15:11
1

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 !!!

Community
  • 1
  • 1
vinesh
  • 4,745
  • 6
  • 41
  • 45
0

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()
Matt Campbell
  • 1,967
  • 1
  • 22
  • 34