2009-11-27 13 views
19

En Nodejs, prácticamente no hay operaciones de E/S de bloqueo. Esto significa que casi todos los códigos IO de nodejs involucran muchas devoluciones de llamada. Esto se aplica a la lectura y la escritura a/desde bases de datos, archivos, procesos, etc. Un ejemplo típico de esto es la siguiente:Gestión de una gran cantidad de recurrencias de devolución de llamada en Nodejs

var useFile = function(filename,callback){ 
    posix.stat(filename).addCallback(function (stats) { 
     posix.open(filename, process.O_RDONLY, 0666).addCallback(function (fd) { 
      posix.read(fd, stats.size, 0).addCallback(function(contents){ 
       callback(contents); 
      }); 
     }); 
    }); 
}; 

... 

useFile("test.data",function(data){ 
    // use data.. 
}); 

Estoy anticipando la escritura de código que hará que muchos operaciones de IO, así que espero para escribir muchos callbacks. Estoy bastante cómodo con el uso de devoluciones de llamada, pero estoy preocupado por la recursividad. ¿Estoy en peligro de tener demasiada recursividad y soplar a través de una pila en alguna parte? Si hago miles de escrituras individuales en mi tienda de valores-clave con miles de devoluciones de llamadas, ¿mi programa finalmente se bloqueará?

¿Estoy malentendiendo o subestimando el impacto? Si no, ¿hay alguna forma de evitar esto mientras se usa el estilo de codificación de devolución de llamada de Nodejs?

+0

El tema es antiguo pero verifique la asincronía: https://github.com/caolan/async – fider

Respuesta

24

Ninguno del código que muestra está utilizando recursividad. Cuando llame al useFile, llama al posix.stat(), que regresa, y useFile termina cuando se ha ejecutado hasta su finalización. En algún momento posterior, cuando la llamada a posix.stat() haya completado dentro del sistema subyacente y los resultados estén disponibles, se ejecutará la función de devolución de llamada que haya agregado. Eso llama al posix.open(), y luego finaliza a medida que se ejecuta hasta su finalización. Una vez que el archivo se haya abierto correctamente, se ejecutará la función de devolución de llamada para que se ejecutará, llamando al posix.read(), y luego finalizará, ya que también se ha ejecutado hasta su finalización. Finalmente, cuando los resultados de la lectura estén disponibles, se ejecutará la función más interna.

El punto importante es que cada función se ejecuta hasta su finalización, ya que las llamadas a las funciones posix.*() son no bloqueantes: es decir, vuelven inmediatamente, provocando que se inicie algo de magia en el sistema subyacente. Así que cada una de sus funciones termina, y más tarde un evento hará que se ejecute la siguiente función; pero en ningún punto hay recursión.

La estructura anidada del código puede dar la impresión de que las cosas internas tendrán que terminar antes de que las cosas que están fuera lleguen a su propio punto final. Pero en este estilo de programación asincrónica basada en eventos, tiene más sentido ver la anidación en términos de más profundo => sucede-después-que.

EDITAR: Intente agregar algunas instrucciones de registro inmediatamente antes del final de cada función anidada; esto ayudará a ilustrar que el orden en que se completan es de afuera hacia adentro.

+0

Gracias por aclarar mis pensamientos confusos sobre este asunto. Todavía necesito trabajar con nodejs un poco más y hacer más pruebas (como el registro que sugieres) para convencerme de que no estoy construyendo una montaña de telescopios ni nada por el estilo, pero este es un paso en ese sentido. dirección. Gracias de nuevo – Maciek

0

Al igual que con cualquier JavaScript, es posible hacer llamadas recursivas con Node.js. Si se encuentra con problemas de profundidad de recursión (como señala NickFitz, no parece estar en peligro), a menudo puede volver a escribir el código para usar un temporizador de intervalo.

1

Tus cosas están bien. Hago llamadas recursivas en Express para seguir redirecciones HTTP, pero lo que estás haciendo es "transversal" y no recursividad

3

mismo ejemplo, la salida de depuración añadido (véase más adelante para la salida):

usefile.JS:

var sys = require("sys"), 
    posix = require("posix"); 

var useFile = function(filename,callback){ 
    posix.stat(filename).addCallback(function (stats) { 
     posix.open(filename, process.O_RDONLY, 0666).addCallback(function (fd) { 
      posix.read(fd, stats.size, 0).addCallback(function(contents){ 
       callback(contents); 
       sys.debug("useFile callback returned"); 
      }); 
      sys.debug("read returned"); 
     }); 
     sys.debug("open returned"); 
    }); 
    sys.debug("stat returned"); 
}; 

useFile("usefile.js",function(){}); 

de salida:

DEBUG: stat returned 
DEBUG: open returned 
DEBUG: read returned 
DEBUG: useFile callback returned 
3

Puede probar

http://github.com/creationix/do

o hágalo usted mismo como lo hice. No importa la manipulación por ahora perdido error (simplemente ignorar eso);)

var sys = require('sys'); 

var Simplifier = exports.Simplifier = function() {} 

Simplifier.prototype.execute = function(context, functions, finalFunction) { 
    this.functions = functions; 
    this.results = {}; 
    this.finalFunction = finalFunction; 
    this.totalNumberOfCallbacks = 0 
    this.context = context; 
    var self = this; 

    functions.forEach(function(f) { 
    f(function() { 
     self.totalNumberOfCallbacks = self.totalNumberOfCallbacks + 1; 
     self.results[f] = Array.prototype.slice.call(arguments, 0);  
     if(self.totalNumberOfCallbacks >= self.functions.length) { 
     // Order the results by the calling order of the functions 
     var finalResults = []; 
     self.functions.forEach(function(f) { 
      finalResults.push(self.results[f][0]); 
     }) 
     // Call the final function passing back all the collected results in the right order 
     finalFunction.apply(self.context, finalResults); 
     } 
    }); 
    }); 
} 

Y un ejemplo sencillo de usarlo

// Execute 
new simplifier.Simplifier().execute(
    // Context of execution 
    self, 
    // Array of processes to execute before doing final handling 
    [function(callback) { 
     db.collection('githubusers', function(err, collection) { 
     collection.find({}, {limit:30}, function(err, cursor) { 
      cursor.toArray(function(err, users) { callback(users); }) 
     }); 
     });  
    }, 

    function(callback) { 
     db.collection('githubprojects', function(err, collection) { 
     collection.find({}, {limit:45, sort:[['watchers', -1]]}, function(err, cursor) { 
      cursor.toArray(function(err, projects) { callback(projects); }) 
     }); 
     });    
    } 
    ], 
    // Handle the final result 
    function(users, projects) { 
    // Do something when ready 
    } 
); 
1

también echar un vistazo a 'paso' (http://github.com/creationix/step) o 'flujo-js 'en github. Esto le permite escribir flujos de devolución de llamada en un estilo más natural. Esto también dejará en claro que no hay recurrencia.

Cuestiones relacionadas