Каков самый чистый способ написать неблокирующий цикл 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.
два варианта, о которых я знаю:
- использовать несколько таймеров для обработки очереди. Они будут чередоваться, что даст чистый эффект "обработки элементов чаще" (это также хороший способ украсть больше CPU ; -), или,
- сделайте больше работы в цикл,или отсчет или основанное время.
Я не уверен, что веб-работники применимо / доступно.
удачи в кодировании.