Каков самый чистый способ написать неблокирующий цикл for в javascript?

Итак, я думал о мозговом тизере - что, если бы у меня был большой объект, который я по какой-то причине должен был перебирать в узле 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 мс, но я не уверен, как сделать цикл с гораздо более быстрым процессом.nextTick.

в моих тестах я обнаружил, что этот пример занимает 7 мс для запуска, в отличие от собственного цикла for (с проверками hasOwnProperty (), регистрируя ту же информацию), который занимает 4 мс.

Итак, каков самый чистый/быстрый способ написать этот же код с помощью узла.Яш?

4 ответов


поведение 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 модуль я написал, чтобы сделать что-то похожее на намерения первоначального вопроса.


здесь можно многое сказать.

  • если у вас есть веб-приложение, например, вы не хотели бы делать "тяжелый подъем" в процессе этого приложения. Несмотря на то, что ваш алгоритм эффективен, он, скорее всего, замедлит работу приложения.
  • в зависимости от того, чего вы пытаетесь достичь, вы, вероятно, будете использовать один из следующих подходов:

    a) поместите цикл" for in " в дочерний процесс и получите результат в главном приложении один раз все кончено!--7--> b) если вы пытаетесь достичь чего-то вроде отложенных заданий (например, отправка электронной почты), вы должны попробовать https://github.com/LearnBoost/kue
    c) создайте собственную программу Kue, используя Redis для связи между основным приложением и приложением "heavy lifting".

для этих подходов вы также можете использовать несколько процессов (для параллелизма).

теперь время для примера кода (это может быть не идеально, так что если у вас есть лучше предложение, пожалуйста, поправьте меня):

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

вместо:

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 итераций цикла, затем вернуть управление в систему на мгновение, а затем забрать, где он остановился, до его завершения.

Edit: здесь он адаптирован для вашего конкретного случая (и с количеством итераций, которые он выполняет за раз, переданный в качестве аргумента):

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

следующее относится к [браузеру] JavaScript; это может быть совершенно не относится к узлу.js.


два варианта, о которых я знаю:

  1. использовать несколько таймеров для обработки очереди. Они будут чередоваться, что даст чистый эффект "обработки элементов чаще" (это также хороший способ украсть больше CPU ; -), или,
  2. сделайте больше работы в цикл,или отсчет или основанное время.

Я не уверен, что веб-работники применимо / доступно.

удачи в кодировании.