Javascript异步循环处理 [英] Javascript async loop processing

查看:29
本文介绍了Javascript异步循环处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个需要一些时间来处理的 javascript 循环.我希望我可以缩小它,但它必须处理大量数据.当它运行时,浏览器当然没有响应.我读过在 javascript 中处理这个问题的最好方法是使用某种异步循环.这样鼠标点击等可以在循环处理之间继续处理.是否有任何标准的异步框架可以很好地解决这个问题?或者有人可以提供一个简单的例子来说明如何编码?谢谢!

解决方案

遗憾的是,WebWorkers 尚未在每个人的浏览器上可用.我一直在使用setTimeout(Func,0);"大约一年的伎俩.这是我最近写的一些研究来解释如何加快速度.如果您只是想要答案,请跳到第 4 步.第 1 步、第 2 步和第 3 步解释推理和机制;

//深入分析 setTimeout(Func,0) 技巧.///////setTimeout(Func,0) 第 1 步/////////////setTimeout 和 setInterval 强加一个最小值//时间限制约为 2 到 10 毫秒.console.log("开始");var workCounter=0;var WorkHard = function(){if(workCounter>=2000) {console.log("done");返回;}工作计数器++;setTimeout(WorkHard,0);};//这大约需要 9 秒//每次迭代大约需要 4.5 毫秒//现在这里有一个微妙的规则,你可以调整//这个最小值是从执行 setTimeout 的时间开始计算的.//所以:console.log("开始");var workCounter=0;var WorkHard = function(){if(workCounter>=2000) {console.log("done");返回;}setTimeout(WorkHard,0);工作计数器++;};//这段代码稍微快一点,因为我们注册了setTimeout//前面的一行代码.其实速度差是无法估量的//在这种情况下,但这个概念是正确的.步骤 2 显示了一个可衡量的示例.//////////////////////////////////////////////////////setTimeout(Func,0) 第 2 步/////////////这是第 1 步中涵盖的概念的一个可衡量的示例.var StartWork = function(){console.log("开始");var startTime = new Date();var workCounter=0;var sum=0;var WorkHard = function(){如果(工作计数器> = 2000){var ms = (new Date()).getTime() - startTime.getTime();console.log("done: sum=" + sum + " time=" + ms + "ms");返回;}for(var i=0; i<1500000; i++) {sum++;}工作计数器++;setTimeout(WorkHard,0);};努力工作();};//这给工作增加了一些难度,而不仅仅是增加一个数字//这会打印完成:sum=3000000000 time=18809ms".//所以花了 18.8 秒.var StartWork = function(){console.log("开始");var startTime = new Date();var workCounter=0;var sum=0;var WorkHard = function(){如果(工作计数器> = 2000){var ms = (new Date()).getTime() - startTime.getTime();console.log("done: sum=" + sum + " time=" + ms + "ms");返回;}setTimeout(WorkHard,0);for(var i=0; i<1500000; i++) {sum++;}工作计数器++;};努力工作();};//现在,按照我们的计划,我们将 setTimeout 移到困难部分之前//这会打印:完成:sum=3000000000 time=12680ms"//所以花了 12.6 秒.稍微数学一下,(18.8-12.6)/2000 = 3.1ms//我们有效地将原来 4.5ms 的死区时间缩短了 3.1ms.//假设其中一些时间可能归因于函数调用和变量//实例化,我们已经消除了 setTimeout 强加的等待时间.//经验教训:如果你想使用 setTimeout(Func,0) 技巧和高//考虑到性能,确保你的函数花费超过 4.5ms,并设置//函数开始时的下一次超时,而不是结束时.//////////////////////////////////////////////////////setTimeout(Func,0) 第三步/////////////步骤 2 的结果很有教育意义,但它并没有真正告诉我们如何应用//现实世界的概念.第 2 步说确保您的函数需要超过 4.5 毫秒".//没有人制作需要 4.5 毫秒的函数.函数要么需要几微秒,//或几秒钟,或几分钟.这个神奇的 4.5ms 是可望而不可及的.//为了解决这个问题,我们引入了Burn Time"的概念.//让我们假设您可以将困难的功能分解为//几毫秒或更短的时间来完成.然后燃烧时间的概念说,//处理几个单独的部分,直到我们达到 4.5 毫秒,然后退出"//步骤 1 显示了一个异步函数,但需要 9 秒才能运行.事实上//我们可以在一毫秒内轻松地将 workCounter 增加 2000 次.//所以,呃,这不应该是异步的,太可怕了.但如果你不知道怎么办//你需要增加多少次数字,也许你需要循环运行 20 次,//也许你需要运行循环 20 亿次.console.log("开始");var startTime = new Date();var workCounter=0;for(var i=0; i<2000000000; i++)//20 亿{工作计数器++;}var ms = (new Date()).getTime() - startTime.getTime();console.log("完成:workCounter=" + workCounter + " time=" + ms + "ms");//打印:完成:workCounter=2000000000 时间=7214ms"//所以花了 7.2 秒.我们可以把它分解成更小的部分吗?是的.//我知道,这是一个弱智的例子,请耐心等待.console.log("开始");var startTime = new Date();var workCounter=0;var each = function(){工作计数器++;};for(var i=0; i<20000000; i++)//2000 万{每个();}var ms = (new Date()).getTime() - startTime.getTime();console.log("完成:workCounter=" + workCounter + " time=" + ms + "ms");//最简单的方法是把它分成 20 亿个小块,每个小块//只有几皮秒才能运行.好吧,实际上,我将数字从 20 亿减少//到 2000 万(少 100 倍).仅仅增加一个函数调用就增加了循环的复杂性//100 倍.其他主题的好课.//打印:完成:workCounter=20000000 时间=7648ms"//所以花了 7.6 秒,这是一个很好的起点.//现在,让我们在异步部分中加入刻录概念console.log("开始");var startTime = new Date();var workCounter=0;无功指数=0;无功结束 = 20000000;var each = function(){工作计数器++;};var 工作 = 函数(){varburnTimeout = new Date();BurnTimeout.setTime(burnTimeout.getTime() + 4.5);//未来的burnTimeout设置为4.5mswhile((new Date()) =结束){var ms = (new Date()).getTime() - startTime.getTime();console.log("完成:workCounter=" + workCounter + " time=" + ms + "ms");返回;}每个();指数++;}设置超时(工作,0);};//打印完成:workCounter=20000000 时间=107119ms"//亲爱的耶稣,我将 7.6 秒的功能增加到 107.1 秒.//但它确实可以防止浏览器锁定,所以我想这是一个加分项.//同样,这里的实际目标只是增加 workCounter,所以所有的开销//相比之下,异步垃圾是巨大的.//无论如何,让我们从第 2 步的建议开始,将 setTimeout 移到困难部分的上方.console.log("开始");var startTime = new Date();var workCounter=0;无功指数=0;无功结束 = 20000000;var each = function(){工作计数器++;};var 工作 = 函数(){如果(索引>=结束){返回;}设置超时(工作,0);varburnTimeout = new Date();BurnTimeout.setTime(burnTimeout.getTime() + 4.5);//未来的burnTimeout设置为4.5mswhile((new Date()) =结束){var ms = (new Date()).getTime() - startTime.getTime();console.log("完成:workCounter=" + workCounter + " time=" + ms + "ms");返回;}每个();指数++;}};//这意味着我们还必须立即检查索引,因为最后一次迭代将无关紧要//打印完成:workCounter=20000000 时间=52892ms"//所以,花了 52.8 秒.改进,但比原生 7.6 秒慢得多.//刻录时间是您调整以在本机循环速度之间取得良好平衡的数字//和浏览器响应能力.让我们把它从 4.5ms 改为 50ms,因为我们真的不需要更快//比 50ms gui 响应.console.log("开始");var startTime = new Date();var workCounter=0;无功指数=0;无功结束 = 20000000;var each = function(){工作计数器++;};var 工作 = 函数(){如果(索引>=结束){返回;}设置超时(工作,0);varburnTimeout = new Date();BurnTimeout.setTime(burnTimeout.getTime() + 50);//未来的burnTimeout设置为50mswhile((new Date()) =结束){var ms = (new Date()).getTime() - startTime.getTime();console.log("完成:workCounter=" + workCounter + " time=" + ms + "ms");返回;}每个();指数++;}};//打印完成:workCounter=20000000 time=52272ms"//所以花了 52.2 秒.这里没有真正的改进,证明 setTimeout 的强加限制//只要燃烧时间超过 4.5 毫秒就被淘汰了//////////////////////////////////////////////////////setTimeout(Func,0) 第 4 步/////////////步骤 3 的性能数据看起来很糟糕,但 GUI 响应能力通常是值得的.//这是一个包含这些概念并提供下降接口的简短库.var WilkesAsyncBurn = function(){var Now = function() {return (new Date());};var CreateFutureDate = 函数(毫秒){var t = 现在();t.setTime(t.getTime() + 毫秒);返回 t;};var For = function(start, end, eachCallback, finalCallback, msBurnTime){var i = 开始;var Each = function(){if(i==-1) {return;}//总是最后一个,无事可做设置超时(每个,0);varburnTimeout = CreateFutureDate(msBurnTime);while(Now() =end) {i=-1;最终回调();返回;}eachCallback(i);我++;}};每个();};var ForEach = 函数(数组,eachCallback,finalCallback,msBurnTime){变量 i = 0;var len = array.length;var Each = function(){如果(i==-1){返回;}设置超时(每个,0);varburnTimeout = CreateFutureDate(msBurnTime);while(Now() =len) {i=-1;最终回调(数组);返回;}eachCallback(i, array[i]);我++;}};每个();};var pub = {};pub.For = For;//每个回调(索引);最终回调();pub.ForEach = ForEach;//每个回调(索引,值);最终回调(数组);WilkesAsyncBurn = 酒吧;};//////////////////////////////////////////////////////setTimeout(Func,0) 第 5 步/////////////以下是如何使用第 4 步中的库的示例.WilkesAsyncBurn();//初始化库console.log("开始");var startTime = new Date();var workCounter=0;var FuncEach = function(){if(workCounter%1000==0){var s = "

";var div = jQuery("*[class~=r1]");div.append(s);}工作计数器++;};var FuncFinal = function(){var ms = (new Date()).getTime() - startTime.getTime();console.log("完成:workCounter=" + workCounter + " time=" + ms + "ms");};WilkesAsyncBurn.For(0,2000000,FuncEach,FuncFinal,50);//打印:完成:workCounter=20000000 时间=149303ms"//还将几千个 div 添加到 html 页面,一次大约 20 个.//浏览器一直响应,任务完成//经验教训:如果你的代码片段非常小,比如增加一个数字,或者遍历//一个对数字求和的数组,然后把它放在一个每个"函数中会害死你.//你仍然可以在这里使用这个概念,但是你的each"函数也应该有一个 for 循环//手动烧掉几百个项目.///////////////////////////////////////////////

I have a javascript loop that takes some time to process. I wish I could slim it down but it has to process a large amount of data. While it's running the browser becomes unresponsive of course. I've read the best way to handle this in javascript is using an asynchronous loop of some sort. This way mouse clicks, etc, can continue to be processed in between loop processing. Is there any standard async frameworks that will work well for this? Or can someone provide a simple example of how this might be coded? Thanks!

解决方案

Sadly WebWorkers are not available yet on everyone's browser. I have been using the "setTimeout(Func,0);" trick for about year. Here is some recent research i wrote up to explain how to speed it up a bit. If you just want the answer, skip to Step 4. Step 1 2 and 3 explain the reasoning and mechanics;

// In Depth Analysis of the setTimeout(Func,0) trick.

//////// setTimeout(Func,0) Step 1 ////////////
// setTimeout and setInterval impose a minimum 
// time limit of about 2 to 10 milliseconds.

  console.log("start");
  var workCounter=0;
  var WorkHard = function()
  {
    if(workCounter>=2000) {console.log("done"); return;}
    workCounter++;
    setTimeout(WorkHard,0);
  };

// this take about 9 seconds
// that works out to be about 4.5ms per iteration
// Now there is a subtle rule here that you can tweak
// This minimum is counted from the time the setTimeout was executed.
// THEREFORE:

  console.log("start");
  var workCounter=0;
  var WorkHard = function()
  {
    if(workCounter>=2000) {console.log("done"); return;}
    setTimeout(WorkHard,0);
    workCounter++;
  };

// This code is slightly faster because we register the setTimeout
// a line of code earlier. Actually, the speed difference is immesurable 
// in this case, but the concept is true. Step 2 shows a measurable example.
///////////////////////////////////////////////


//////// setTimeout(Func,0) Step 2 ////////////
// Here is a measurable example of the concept covered in Step 1.

  var StartWork = function()
  {
    console.log("start");
    var startTime = new Date();
    var workCounter=0;
    var sum=0;
    var WorkHard = function()
    {
      if(workCounter>=2000) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: sum=" + sum + " time=" + ms + "ms"); 
        return;
      }
      for(var i=0; i<1500000; i++) {sum++;}
      workCounter++;
      setTimeout(WorkHard,0);
    };
    WorkHard();
  };

// This adds some difficulty to the work instead of just incrementing a number
// This prints "done: sum=3000000000 time=18809ms".
// So it took 18.8 seconds.

  var StartWork = function()
  {
    console.log("start");
    var startTime = new Date();
    var workCounter=0;
    var sum=0;
    var WorkHard = function()
    {
      if(workCounter>=2000) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: sum=" + sum + " time=" + ms + "ms"); 
        return;
      }
      setTimeout(WorkHard,0);
      for(var i=0; i<1500000; i++) {sum++;}
      workCounter++;
    };
    WorkHard();
  };

// Now, as we planned, we move the setTimeout to before the difficult part
// This prints: "done: sum=3000000000 time=12680ms"
// So it took 12.6 seconds. With a little math, (18.8-12.6)/2000 = 3.1ms
// We have effectively shaved off 3.1ms of the original 4.5ms of dead time.
// Assuming some of that time may be attributed to function calls and variable 
// instantiations, we have eliminated the wait time imposed by setTimeout.

// LESSON LEARNED: If you want to use the setTimeout(Func,0) trick with high 
// performance in mind, make sure your function takes more than 4.5ms, and set 
// the next timeout at the start of your function, instead of the end.
///////////////////////////////////////////////


//////// setTimeout(Func,0) Step 3 ////////////
// The results of Step 2 are very educational, but it doesn't really tell us how to apply the
// concept to the real world.  Step 2 says "make sure your function takes more than 4.5ms".
// No one makes functions that take 4.5ms. Functions either take a few microseconds, 
// or several seconds, or several minutes. This magic 4.5ms is unattainable.

// To solve the problem, we introduce the concept of "Burn Time".
// Lets assume that you can break up your difficult function into pieces that take 
// a few milliseconds or less to complete. Then the concept of Burn Time says, 
// "crunch several of the individual pieces until we reach 4.5ms, then exit"

// Step 1 shows a function that is asyncronous, but takes 9 seconds to run. In reality
// we could have easilly incremented workCounter 2000 times in under a millisecond.
// So, duh, that should not be made asyncronous, its horrible. But what if you don't know
// how many times you need to increment the number, maybe you need to run the loop 20 times,
// maybe you need to run the loop 2 billion times.

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  for(var i=0; i<2000000000; i++) // 2 billion
  {
    workCounter++;
  }
  var ms = (new Date()).getTime() - startTime.getTime();
  console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 

// prints: "done: workCounter=2000000000 time=7214ms"
// So it took 7.2 seconds. Can we break this up into smaller pieces? Yes.
// I know, this is a retarded example, bear with me.

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var each = function()
  {
    workCounter++;
  };
  for(var i=0; i<20000000; i++) // 20 million
  {
    each();
  }
  var ms = (new Date()).getTime() - startTime.getTime();
  console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 

// The easiest way is to break it up into 2 billion smaller pieces, each of which take 
// only several picoseconds to run. Ok, actually, I am reducing the number from 2 billion
// to 20 million (100x less).  Just adding a function call increases the complexity of the loop
// 100 fold. Good lesson for some other topic.
// prints: "done: workCounter=20000000 time=7648ms"
// So it took 7.6 seconds, thats a good starting point.
// Now, lets sprinkle in the async part with the burn concept

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var index=0;
  var end = 20000000;
  var each = function()
  {
    workCounter++;
  };
  var Work = function()
  {
    var burnTimeout = new Date();
    burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
    while((new Date()) < burnTimeout)
    {
      if(index>=end) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
        return;
      }
      each();
      index++;
    }
    setTimeout(Work,0);
  };

// prints "done: workCounter=20000000 time=107119ms"
// Sweet Jesus, I increased my 7.6 second function to 107.1 seconds.
// But it does prevent the browser from locking up, So i guess thats a plus.
// Again, the actual objective here is just to increment workCounter, so the overhead of all
// the async garbage is huge in comparison. 
// Anyway, Lets start by taking advice from Step 2 and move the setTimeout above the hard part. 

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var index=0;
  var end = 20000000;
  var each = function()
  {
    workCounter++;
  };
  var Work = function()
  {
    if(index>=end) {return;}
    setTimeout(Work,0);
    var burnTimeout = new Date();
    burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
    while((new Date()) < burnTimeout)
    {
      if(index>=end) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
        return;
      }
      each();
      index++;
    }
  };

// This means we also have to check index right away because the last iteration will have nothing to do
// prints "done: workCounter=20000000 time=52892ms"  
// So, it took 52.8 seconds. Improvement, but way slower than the native 7.6 seconds.
// The Burn Time is the number you tweak to get a nice balance between native loop speed
// and browser responsiveness. Lets change it from 4.5ms to 50ms, because we don't really need faster
// than 50ms gui response.

  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var index=0;
  var end = 20000000;
  var each = function()
  {
    workCounter++;
  };
  var Work = function()
  {
    if(index>=end) {return;}
    setTimeout(Work,0);
    var burnTimeout = new Date();
    burnTimeout.setTime(burnTimeout.getTime() + 50); // burnTimeout set to 50ms in the future
    while((new Date()) < burnTimeout)
    {
      if(index>=end) 
      {
        var ms = (new Date()).getTime() - startTime.getTime();
        console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
        return;
      }
      each();
      index++;
    }
  };

// prints "done: workCounter=20000000 time=52272ms"
// So it took 52.2 seconds. No real improvement here which proves that the imposed limits of setTimeout
// have been eliminated as long as the burn time is anything over 4.5ms
///////////////////////////////////////////////


//////// setTimeout(Func,0) Step 4 ////////////
// The performance numbers from Step 3 seem pretty grim, but GUI responsiveness is often worth it.
// Here is a short library that embodies these concepts and gives a descent interface.

  var WilkesAsyncBurn = function()
  {
    var Now = function() {return (new Date());};
    var CreateFutureDate = function(milliseconds)
    {
      var t = Now();
      t.setTime(t.getTime() + milliseconds);
      return t;
    };
    var For = function(start, end, eachCallback, finalCallback, msBurnTime)
    {
      var i = start;
      var Each = function()
      {
        if(i==-1) {return;} //always does one last each with nothing to do
        setTimeout(Each,0);
        var burnTimeout = CreateFutureDate(msBurnTime);
        while(Now() < burnTimeout)
        {
          if(i>=end) {i=-1; finalCallback(); return;}
          eachCallback(i);
          i++;
        }
      };
      Each();
    };
    var ForEach = function(array, eachCallback, finalCallback, msBurnTime)
    {
      var i = 0;
      var len = array.length;
      var Each = function()
      {
        if(i==-1) {return;}
        setTimeout(Each,0);
        var burnTimeout = CreateFutureDate(msBurnTime);
        while(Now() < burnTimeout)
        {
          if(i>=len) {i=-1; finalCallback(array); return;}
          eachCallback(i, array[i]);
          i++;
        }
      };
      Each();
    };

    var pub = {};
    pub.For = For;          //eachCallback(index); finalCallback();
    pub.ForEach = ForEach;  //eachCallback(index,value); finalCallback(array);
    WilkesAsyncBurn = pub;
  };

///////////////////////////////////////////////


//////// setTimeout(Func,0) Step 5 ////////////
// Here is an examples of how to use the library from Step 4.

  WilkesAsyncBurn(); // Init the library
  console.log("start");
  var startTime = new Date();
  var workCounter=0;
  var FuncEach = function()
  {
    if(workCounter%1000==0)
    {
      var s = "<div></div>";
      var div = jQuery("*[class~=r1]");
      div.append(s);
    }
    workCounter++;
  };
  var FuncFinal = function()
  {
    var ms = (new Date()).getTime() - startTime.getTime();
    console.log("done: workCounter=" + workCounter + " time=" + ms + "ms"); 
  };
  WilkesAsyncBurn.For(0,2000000,FuncEach,FuncFinal,50);

// prints: "done: workCounter=20000000 time=149303ms"
// Also appends a few thousand divs to the html page, about 20 at a time.
// The browser is responsive the entire time, mission accomplished

// LESSON LEARNED: If your code pieces are super tiny, like incrementing a number, or walking through 
// an array summing the numbers, then just putting it in an "each" function is going to kill you. 
// You can still use the concept here, but your "each" function should also have a for loop in it 
// where you burn a few hundred items manually.  
///////////////////////////////////////////////

这篇关于Javascript异步循环处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆