infinito - ¿Cuál es la forma más limpia de escribir un bucle no bloqueante para javascript?
condicion for en javascript (4)
Por lo tanto, he estado pensando en un desafío para la mente: ¿qué pasaría si tuviera un objeto grande que, por alguna razón, tuviera que recorrer en el nodo js y no quisiera bloquear el bucle de eventos mientras estaba haciendo eso?
Aquí hay un ejemplo fuera de lo alto de mi cabeza, estoy seguro de que puede ser mucho más limpio:
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);
}
Aunque estoy seguro de que hay otras razones para que sea desordenado, esto se ejecutará más lentamente que un ciclo normal, porque setInterval 0 no se ejecuta cada 0 ms, pero no estoy seguro de cómo hacer un ciclo con la cantidad suficiente. proceso más rápido. siguienteTick.
En mis pruebas, encontré que este ejemplo tarda 7 ms en ejecutarse, a diferencia de un nativo para bucle (con las comprobaciones hasOwnProperty (), registrando la misma información), que toma 4 ms.
Entonces, ¿cuál es la forma más limpia / rápida de escribir este mismo código usando node.js?
El comportamiento de process.nextTick
ha cambiado desde que se hizo la pregunta. Las respuestas anteriores tampoco siguieron la pregunta según la limpieza y la eficiencia de la función.
// 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);
}
})();
}
Tome nota de los comentarios de @alessioalex con respecto a Kue y la cola de trabajos adecuada.
Ver también: share-time , un módulo que escribí para hacer algo similar a la intención de la pregunta original.
En lugar de:
for (var i=0; i<len; i++) {
doSomething(i);
}
hacer algo como esto:
var i = 0, limit;
while (i < len) {
limit = (i+100);
if (limit > len)
limit = len;
process.nextTick(function(){
for (; i<limit; i++) {
doSomething(i);
}
});
}
}
Esto ejecutará 100 iteraciones del bucle, luego devolverá el control al sistema por un momento, luego retomará donde lo dejó, hasta que esté listo.
Edición: aquí se adapta a su caso particular (y con el número de iteraciones que realiza en un momento dado como un argumento):
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);
}
});
}
}
Hay muchas cosas que decir aquí.
- Si tiene una aplicación web, por ejemplo, no querría hacer "trabajo pesado" en el proceso de esa aplicación. A pesar de que su algoritmo es eficiente, probablemente ralentizaría la aplicación.
- Dependiendo de lo que esté tratando de lograr, probablemente utilice uno de los siguientes enfoques:
a) ponga su ciclo "for in" en un proceso secundario y obtenga el resultado en su aplicación principal una vez que haya terminado
b) Si está tratando de lograr algo como trabajos retrasados (por ejemplo, enviar correos electrónicos), debe intentar https://github.com/LearnBoost/kue
c) haga su propio programa similar a Kue utilizando Redis para comunicarse entre la aplicación principal y la aplicación de "trabajo pesado".
Para estos enfoques, también podría utilizar múltiples procesos (para la concurrencia).
Ahora es el momento para un código de muestra (puede que no sea perfecto, así que si tiene una sugerencia mejor, por favor, corríjame):
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);
Lo siguiente se aplica a [navegador] JavaScript; puede ser completamente irrelevante para node.js.
Dos opciones que conozco:
- Utilice múltiples temporizadores para procesar la cola. Se intercalarán, lo que dará el efecto neto de "procesar elementos más a menudo" (esta también es una buena manera de robar más CPU ;-), o
- Hacer más trabajo por ciclo, ya sea por cuenta o por tiempo.
No estoy seguro de si los Trabajadores Web son aplicables / disponibles.
Feliz codificacion