21

所以,我一直在考虑一个脑筋急转弯——如果我有一个大对象我出于某种原因不得不在节点 js 中迭代,并且不想在我这样做的时候阻塞事件循环怎么办?

这是一个令人难以置信的例子,我相信它可以更干净:

var forin = function(obj,callback){
    var keys = Object.keys(obj),
        index = 0,
        interval = setInterval(function(){
            if(index < keys.length){
                callback(keys[index],obj[keys[index]],obj);
            } else {
                clearInterval(interval);
            }
            index ++;
        },0);
}

虽然我确信还有其他原因导致它变得混乱,但这将比常规的 for 循环执行得慢,因为 setInterval 0 实际上并不是每 0 毫秒执行一次,但我不确定如何使用 much更快的 process.nextTick。

在我的测试中,我发现这个示例需要 7 毫秒才能运行,而本机 for 循环(使用 hasOwnProperty() 检查,记录相同的信息)需要 4 毫秒。

那么,使用 node.js 编写相同代码的最简洁/最快的方法是什么?

4

4 回答 4

5

process.nextTick自提出问题以来,的行为发生了变化。根据功能的清洁度和效率,先前的答案也没有遵循问题。

// in node 0.9.0, process.nextTick fired before IO events, but setImmediate did
// not yet exist. before 0.9.0, process.nextTick between IO events, and after
// 0.9.0 it fired before IO events. if setImmediate and process.nextTick are
// both missing fall back to the tick shim.
var tick =
  (root.process && process.versions && process.versions.node === '0.9.0') ?
  tickShim :
  (root.setImmediate || (root.process && process.nextTick) || tickShim);

function tickShim(fn) {setTimeout(fn, 1);}

// executes the iter function for the first object key immediately, can be
// tweaked to instead defer immediately
function asyncForEach(object, iter) {
  var keys = Object.keys(object), offset = 0;

  (function next() {
    // invoke the iterator function
    iter.call(object, keys[offset], object[keys[offset]], object);

    if (++offset < keys.length) {
      tick(next);
    }
  })();
}

请注意@alessioalex关于 Kue 和正确的工作队列的评论。

另请参阅:share-time,我编写的一个模块,用于执行与原始问题的意图类似的操作。

于 2015-02-04T07:39:56.897 回答
2

这里有很多话要说。

  • 例如,如果您有一个 Web 应用程序,您不会希望在该应用程序的进程中进行“繁重的工作”。即使您的算法很有效,它仍然很可能会减慢应用程序的速度。
  • 根据您要实现的目标,您可能会使用以下方法之一:

    a)将“for in”循环放在子进程中,并在结束后在主应用程序中获取结果
    b)如果您正在尝试要实现诸如延迟工作(例如发送电子邮件)之类的事情,您应该尝试https://github.com/LearnBoost/kue
    c)使用 Redis 制作您自己的类似 Kue 的程序,以便在主应用程序和“繁重的工作”之间进行通信“ 应用程序。

对于这些方法,您还可以使用多个进程(用于并发)。

现在是示例代码的时候了(它可能并不完美,所以如果您有更好的建议,请纠正我):

var forIn, obj;

// the "for in" loop
forIn = function(obj, callback){
  var keys = Object.keys(obj);
  (function iterate(keys) {
    process.nextTick(function () {
      callback(keys[0], obj[keys[0]]);
      return ((keys = keys.slice(1)).length && iterate(keys));
    });
  })(keys);
};

// example usage of forIn
// console.log the key-val pair in the callback
function start_processing_the_big_object(my_object) {
  forIn(my_object, function (key, val) { console.log("key: %s; val: %s;", key, val); });
}

// Let's simulate a big object here
// and call the function above once the object is created
obj = {};
(function test(obj, i) {
  obj[i--] = "blah_blah_" + i;
  if (!i) { start_processing_the_big_object(obj); }
  return (i && process.nextTick(function() { test(obj, i); }));
})(obj, 30000);
于 2011-11-03T22:39:39.357 回答
1

代替:

for (var i=0; i<len; i++) {
  doSomething(i);
  }

做这样的事情:

var i = 0, limit;
while (i < len) {
  limit = (i+100);
  if (limit > len)
    limit = len;
  process.nextTick(function(){
     for (; i<limit; i++) {
      doSomething(i);
     }
    });
  }
}

这将运行 100 次循环迭代,然后将控制权返回给系统片刻,然后从停止的地方继续,直到完成。

编辑:这里它适用于您的特定情况(并且它一次执行的迭代次数作为参数传入):

var forin = function(obj, callback, numPerChunk){
  var keys = Object.keys(obj);
  var len = keys.length;
  var i = 0, limit;
  while (i < len) {
    limit = i + numPerChunk;
    if (limit > len)
      limit = len;
    process.nextTick(function(){
        for (; i<limit; i++) {
          callback(keys[i], obj[keys[i]], obj);
        }
      });
  }
}
于 2011-11-03T22:51:47.503 回答
-1

以下适用于 [浏览器] JavaScript;它可能与 node.js 完全无关。


我知道的两个选项:

  1. 使用多个计时器来处理队列。它们将交错,这将产生“更频繁地处理项目”的净效果(这也是窃取更多 CPU 的好方法;-),或者,
  2. 每个周期做更多的工作,无论是计数还是时间。

我不确定 Web Workers 是否适用/可用。

快乐编码。

于 2011-11-03T17:16:17.453 回答