在Internet Explorer中禁用长时间运行脚本消息

49

我有一个包含for循环的JavaScript函数,循环次数较多。在调用该函数后,IE浏览器会显示以下信息:

停止运行此脚本?
此页面上的一个脚本正在导致您的Web浏览器运行缓慢。如果它继续运行,您的计算机可能会变得无响应。

如何解决这个问题?
是否有方法可以禁用IE中的此消息?


16
首先,请展示你的代码。其次,禁用IE中此消息的方法是修复您的代码。 - punkrockbuddyholly
你用的是哪个版本的IE浏览器?如果不是代码问题,可以尝试在http://superuser.com/上提问。 - zack
@Eric Leschinski的答案对我有用... - LCJ
您可以使用注册表来完成此操作。请参见这里 - Donald Duck
4个回答

80

当Internet Explorer对一段JavaScript代码执行的同步指令数量达到最大值时,会显示此消息。默认最大值为5,000,000个指令,您可以通过编辑注册表来增加单台计算机上的最大值。

现在Internet Explorer跟踪执行的脚本语句总数,并在每次启动新的脚本执行(例如从定时器或事件处理程序启动)时为当前页面与脚本引擎重置该值。当超过一个阈值时,Internet Explorer会显示一个“长时间运行的脚本”对话框。

解决所有可能查看您的页面的用户的问题的唯一方法是使用定时器分解循环执行的次数,或者重构您的代码,使其不需要处理那么多指令。

使用定时器分解循环的方法相对简单:

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;
        }
    }
})();

4
非常感谢您提供注册表修改的链接。我一直在尝试在虚拟机上运行几个性能测试,但是IE弹出对话框破坏了测试结果。我花了很多时间才找到这个问题,但希望通过添加此评论并提及一些关键词(如“停止运行此脚本?”和“IE弹出对话框”),它会在谷歌搜索结果中更容易被发现。 - Xavi
2
如果您不给setTimeout设置一个明确的时间,即跳过第二个参数,那么这个脚本将会运行得更快,并且您将获得相同的效果。不提供第二个参数告诉浏览器尽快触发传递的函数,而不是“至少经过这么长时间”后再触发。 - David Meister
2
@AndyE - 通常我不包括参数,但我发现jQuery在某些情况下会在0和1毫秒之间切换延迟,这是我不理解的原因(可能是与某些向后兼容性有关),但它从未被省略,所以当我给出建议时我会模仿这个。我知道几年前jQuery对于动画帧有一个硬编码的13ms超时限制,这是一个有点麻烦的问题。也许这个较低的限制是你想到的?或者过去的电脑速度比较慢。 - David Meister
3
@AndyE - 我在圣诞节做了更多实验,并撰写了一篇比直接发布到Stack Overflow更加深入的文章,介绍了一些管理长时间运行脚本和避免超时错误的技巧。认为你可能会感兴趣,链接为http://thedavidmeister.info/post/techniques-help-manage-long-running-scripts-and-avoid-timeout-errors。 - David Meister
2
@DavidMeister,那个链接已经失效了。 - Sam Hasler
显示剩余8条评论

13
不响应的脚本对话框会在某些JavaScript线程执行时间过长时显示。编辑注册表可能有效,但您需要在所有客户端机器上进行操作。您可以使用以下“递归闭包”来缓解问题。这只是一种编码结构,允许您将长时间运行的for循环更改为执行一些工作并跟踪其离开的位置,然后让浏览器暂停,继续之前的工作,直到完成。 图1:将此实用程序类RepeatingOperation添加到您的JavaScript文件中。您不需要更改此代码:
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();
  };
};

图2,以下代码表示您的代码导致“停止运行此脚本”对话框,因为它需要很长时间才能完成:

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.
  }
}

图3. 以下代码是图2中有问题的代码的修复方案。注意,for循环被递归闭包替换,每执行10次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.

};

来源于这个网址:

http://www.picnet.com.au/blogs/Guido/post/2010/03/04/如何防止浏览器弹出“停止运行此脚本”的对话框


4
我认为你不希望这样做,因为RepeatingOperation可能只会被调用1000次,并且由于setTimeout()中的硬编码最短延迟时间,可能导致网站在4-10秒钟内无响应。请参见Andy E答案下的评论线程。 - David Meister

1
在我的情况下,当播放视频时,我需要每次 currentTime 更新时调用一个函数。因此,我使用了视频的 timeupdate 事件,并且我知道它至少每秒触发4次(取决于您使用的浏览器,请参见 this)。所以我将其更改为每秒调用一次函数,如下所示:
var currentIntTime = 0;

var someFunction = function() {
    currentIntTime++;
    // Do something here
} 
vidEl.on('timeupdate', function(){
    if(parseInt(vidEl.currentTime) > currentIntTime) {
        someFunction();
    }
});

这样做可以将对 someFunc 的调用至少减少 1/3,并且可能有助于您的浏览器正常运行。我亲自试过了!

0
我无法评论之前的答案,因为我没有尝试过。然而,我知道以下策略适用于我。它可能不太优雅,但可以完成任务。它也不需要像其他方法一样将代码分成块。在我的情况下,这不是一个选项,因为我的代码对正在循环的逻辑进行了递归调用;即,没有实际的方法可以跳出循环,然后通过使用全局变量来保留当前状态以某种方式恢复,因为这些全局变量可能会被后续递归调用中对它们的引用更改。因此,我需要一种简单直接的方法,不会提供机会让代码破坏数据状态的完整性。
假设“停止脚本?”对话框在执行for()循环后的若干次迭代(在我的情况下,大约8-10次)中出现,并且不能干扰注册表,那么这就是解决方法(至少对我而言):
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()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接