16

我在 jQuery 每个循环中执行“繁重”的画布操作,导致速度较慢的设备(IE 和 iPad)有时变得完全没有响应。

所以我想我可以使用下划线_.defer()将每个循环中的函数排队,例如:

function handleAsset = _.defer(function(){
//weightlifting goes here (partly async)
});

$.each(assets, handleAsset);

然而,这会引发一个奇怪的错误(堆栈跟踪指向$.each):

Uncaught TypeError: Object 20877 has no method 'call'

这种方法有缺陷吗?这是由于处理函数内部正在进行异步操作吗?是否有另一种/更好的方法来实现这一目标?

4

2 回答 2

22

这是有缺陷的。您应该尝试在可能的最低点解耦/分解代码。我认为从长远来看,仅仅解耦循环的每次迭代就足够了。

但是,您真正需要做的是,设置一个异步失控计时器,为实现提供足够的空间来更新UI 队列(或UI 线程)。这通常使用setTimeout()(客户端)、nextTick(node.js)或setImmediate(即将推出)等方法完成。

例如,假设我们有一个数组,我们想要处理每个条目

var data = new Array(10000).join( 'data-' ).split('-'); // create 10.000 entries

function process( elem ) {
    // assume heavy operations
    elem.charAt(1) + elem.charAt(2);
}

for(var i = 0, len = data.length; i < len; i++ ) {
    process( data[i] );
}

现在这段代码是一个经典的循环,遍历数组并处理它的数据。它还会消耗 100% 的 CPU 时间,因此只要需要处理所有条目就会阻塞浏览器UI 队列(这基本上意味着浏览器 UI 将冻结并变得无响应)。

为了避免这种情况,我们可以创建一个这样的结构:

var data  = new Array(10000).join( 'data-' ).split('-'); // create 10.000 entries

function runAsync( data ) {
    var start = Date.now();

    do {
        process( data.shift() );
    } while( data.length && Date.now() - start > 100 );

    if( data.length ) {
        setTimeout( runAsync.bind( null, data ), 100 );
    }
}

runAsync( data.concat() );

这里会发生什么?

我们基本上在做的是:

  • 在100 毫秒的时间范围内获取数组并处理尽可能多的数据/条目
  • 之后,停止处理(调用setTimeout)并让 UI 有机会更新
  • 只要我们在数组中还有数据就这样做

任何超过100 毫秒的延迟通常都会被人眼识别为“滞后”。任何低于它的东西看起来都很流畅和漂亮(至少我们的眼睛会这样告诉我们)。100ms 是一个很好的值作为最大处理时间的限制。我什至建议降低到 50 毫秒。

这里需要注意的是整体处理时间会增加,但我认为处理时间更长并保持响应更好,而不是更快的处理和非常糟糕的用户体验。


快速演示:

于 2012-12-19T15:59:38.573 回答
2

所以你想限制并发异步操作的数量?您的实施中的缺陷是您将推迟每个操作,直到前一个操作完成。

一种选择是使用序列助手,然后您可以将此队列分解为更易于管理的块进行处理。

https://github.com/michiel/asynchelper-js/blob/master/lib/sequencer.js

var actions = [];
$.each(assets, function(key, value) {

    actions.push(function(callback) {
      $.ajax({
          url: 'process.php?id='+val,
          success: function(msg) {

            callback();
          }
        });
    });
  }
);

var sequencer = new Sequencer(actions);
sequencer.start();

如果您将您的操作数组拆分为两个数组,并让它们并排运行,那么您将只能同时运行两个进程,直到两个队列都完成。

例如

var arr1 = actions.splice(0,100);
var arr2 = actions.splice(100,200);

var sequencer1 = new Sequencer(arr1);
sequencer1.start();

var sequencer2 = new Sequencer(arr2);
sequencer2.start();
于 2012-12-19T16:20:48.210 回答