Какой самый чистый способ написать неблокирующий цикл в javascript? - PullRequest
19 голосов
/ 03 ноября 2011

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

Итак, какой самый чистый / быстрый способ написать этот же код с использованием node.js?

Ответы [ 4 ]

3 голосов
/ 04 февраля 2015

Поведение 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 относительно Ку и правильных очередей на работу.

См. Также: share-time , модуль, который я написал, чтобы сделать что-то похожее на намерение исходного вопроса.

1 голос
/ 04 ноября 2011

Здесь можно сказать много вещей.

  • Если у вас есть веб-приложение, например, вы не захотите выполнять «тяжелую работу» в процессе этого приложения. Даже если ваш алгоритм эффективен, он, скорее всего, все равно замедлит работу приложения.
  • В зависимости от того, чего вы пытаетесь достичь, вы, вероятно, будете использовать один из следующих подходов:

    а) поместите свой цикл for в дочерний процесс и получите результат в основном приложении, как только оно закончится.
    б) если вы пытаетесь достичь чего-то вроде отложенной работы (например, для отправки писем), вам следует попробовать https://github.com/LearnBoost/kue
    в) создать собственную программу, похожую на Kue, используя Redis для связи между основным приложением и приложением «тяжелой работы».

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

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

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);
1 голос
/ 04 ноября 2011

Вместо:

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);
        }
      });
  }
}
0 голосов
/ 03 ноября 2011

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


Два известных мне варианта:

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

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

Удачное кодирование.

...