Какой самый чистый способ написать цикл без блокировки в javascript?
Итак, я думал о мозговой дразнилке - что, если бы у меня был большой объект, по какой-то причине мне пришлось перебирать в node 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);
}
Хотя я уверен, что есть другие причины, что это беспорядочно, это будет выполняться медленнее, чем обычный цикл, потому что setInterval 0 фактически не выполняет каждые 0 мс, но я не уверен, как сделать цикл с гораздо более быстрым процессом .nextTick.
В моих тестах я нашел, что этот пример занимает 7 мс для запуска, в отличие от нативного цикла (с проверкой hasOwnProperty(), регистрацией той же информации), которая занимает 4 мс.
Итак, что самый чистый/самый быстрый способ написать этот же код с помощью node.js?
Ответы
Ответ 1
Поведение 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, модуль, который я написал, чтобы сделать что-то похожее на намерение исходного вопроса.
Ответ 2
Здесь есть много вещей.
- Если у вас есть веб-приложение, например, вы не захотите делать "тяжелый подъем" в этом процессе приложения. Несмотря на то, что ваш алгоритм эффективен, все равно, скорее всего, это замедлит работу приложения.
- В зависимости от того, чего вы пытаетесь достичь, вы, вероятно, используете один из следующих способов:
a) поставьте цикл "for in" в дочернем процессе и получите результат в своем основном приложении после его завершения
б) если вы пытаетесь достичь чего-то вроде отложенных заданий (для отправки электронной почты), вы должны попробовать https://github.com/LearnBoost/kue
c) сделайте собственную программу Kue, используя Redis для связи между основным приложением и приложением "тяжелая атлетика".
Для этих подходов вы также можете использовать несколько процессов (для concurrency).
Теперь время для примера кода (возможно, оно не идеально, поэтому, если у вас есть лучшее предложение, пожалуйста, исправьте меня):
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 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);
Ответ 3
Вместо:
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);
}
});
}
}
Ответ 4
В JavaScript [JavaScript] используется JavaScript; это может быть совершенно не имеет отношения к node.js.
Два варианта, о которых я знаю:
- Используйте несколько таймеров для обработки очереди. Они будут чередовать, что даст чистый эффект "обработки элементов" чаще (это также хороший способ украсть больше CPU;-) или,
- Выполняйте больше работы за цикл, будь то счет или время.
Я не уверен, что веб-рабочие применимы/доступны.
Счастливое кодирование.