2012-08-06 21 views
21

Me gustaría inicializar el módulo de manera asíncrona y proponer algunas ideas. Necesito el objeto DB con la lista de colecciones de Mongo y otros datos, pero la lista de archivos en ./ servirá para abreviar.Inicialización asincrónica del módulo Node.js

No puedo exportar la función o la clase porque necesito require('db') para devolver el mismo objeto cada vez.


Primera y más simple lo que vino a la mente es asignar a module.exportsObject y rellenarla después:

var exports = {}; 
module.exports = exports; 

require('fs').readdir('.', function(err, files) { 
    exports.error = err; 
    exports.files = files; 
}); 

Lo malo - que no se sabe muy bien desde el exterior cuando la lista está listo y no hay una buena manera de verificar si hay errores.


Segunda manera que he ComEd con EventEmitter es heredar y notificar a todos que DB está preparado o se produjo el error. Si todo está bien, sigue.

var events = require('events'); 
var util = require('util'); 

function Db() { 
    events.EventEmitter.call(this); 
    this.ready = false; 
    this.files = null; 
    this.initialize(); 
} 

util.inherits(Db, events.EventEmitter); 

Db.prototype.initialize = function() { 
    if (this.ready) 
    return this.emit('ready'); 

    var self = this; 
    require('fs').readdir('.', function(err, files) { 
    if (err) 
     return self.emit('error', err); 

    self.files = files; 
    self.ready = true; 
    self.emit('ready'); 
    }); 
}; 

module.exports = new Db(); 

Y ahora creo que es más razonable:

// db.js 
var exports = {init: init}; 
module.exports = exports; 

function init(callback) { 
    callback = (typeof callback === 'function') ? callback : function() {}; 
    require('fs').readdir('.', function(err, files) { 
    delete exports.init; 
    exports.result = files; // that's pretty much what I need, 
          // so don't mind result slightly differs 
          // from previous cases 
    callback(err); 
    }); 
} 
// main.js 
var db = require('./db'); 

// check for `db.init` presence maybe... 

db.init(function(err) { 
    return err ? console.error('Bad!') 
      : console.log(db); // It works! 
}); 

¿Qué debo escoger y por qué? ¿Qué tan mala es esa idea en general y mis opciones en particular?

Gracias por su comentario.

Respuesta

27

TL; DR:readdirSync() uso en lugar de readdir() si sólo está planeando para leer archivos locales en tiempo de inicio. Si está planeando leer datos de la base de datos remota o hacer alguna E/S en tiempo de ejecución, use su opción n. ° 2: la devolución de llamada. Explicación y ejemplos de código a continuación.

explicación detallada:

Aunque al principio esto puede parecer un/dependecy/pregunta relacionada con el módulo de requerir, en realidad no es. Es una pregunta genérica de cómo manejar código asincrónico. Permítanme explicar:

require() es básicamente la única función síncrona ampliamente utilizada en todo el nodo que se ocupa de E/S (requiere otros módulos del sistema de archivos). Sincrónico significa que en realidad devuelve sus datos como valor de retorno, en lugar de llamar a una devolución de llamada.

El 101 regla más básica en la programación asincrónica es:

Puede Nunca tomar una pieza de código asíncrono y crear una API síncrona para ello.

require utiliza una versión especial sincrónica de readFile llamada readFileSync. Como los módulos realmente solo se cargan al inicio del programa, el hecho de que bloquee la ejecución de node.js mientras lee el módulo no es un problema.

En su ejemplo, sin embargo, intenta realizar E/S asíncronas adicionales - readdir() realizadas durante la etapa requerida. Por lo tanto, debe usar versión síncrona de de este comando o la API debe cambiar ...

Así está el trasfondo de su problema.

identificado las dos opciones básicas:

  1. utilizando una promesa (que es esencialmente el mismo que el ejemplo EventEmitter)
  2. utilizando un devolución de llamada (el segundo ejemplo muestra esto bien) y un tercero es:
  3. usando una versión síncrona del comando readdir() llamada readdirSync()

me gustaría utilizar la opción # 3 por razones de simplicidad - pero sólo si usted está planeando para leer solo un par de archivos en tiempo de inicio como su ejemplo implica. Si más adelante su módulo DB realmente se va a conectar a una base de datos, o si planea hacer algo de esto en tiempo de ejecución, salte el bote ahora y vaya con la API asíncrona.

No muchas personas ya recuerdan esto, pero las promesas eran en realidad el valor predeterminado original de cómo manejar asincronías en node.js. En el nodo 0.1.30 sin embargo, promisses fueron removed and replaced mediante una devolución de llamada estandarizada con la firma function(err, result). Esto se hizo en gran parte por razones de simplicidad.

En estos días, la gran mayoría de sus llamadas asincrónicas toman esta devolución de llamada estándar como el último parámetro. Su controlador de base de datos lo hace, su marco web lo hace, está en todas partes. Debes mantenerte con el diseño predominante y usarlo también.

La única razón para preferir promesas o eventos es si usted tiene varios resultados diferentes que puede pasar. Por ejemplo, se puede abrir un zócalo, recibir datos, cerrar, enrojar, etc.

Este no es su caso. Tu módulo siempre hace lo mismo (lee algunos archivos). Así que opción # 2 es (a menos que pueda permanecer sincrónico).

Por último, aquí están las dos opciones ganadoras reescrito ligeramente:

opción síncrono:
buena sólo para sistema de archivos local en tiempo de inicio

// db.js 
var fs = require('fs'); 
exports = fs.readdirSync('.'); 

// main.js 
var db = require('./db'); 
// insert rest of your main.js code here 

opción asíncrona:
para cuando quiere usar bases de datos, etc.

// db.js 
var fs = require('fs'), cached_files; 

exports.init = function(callback) { 
    if (cached_files) { 
    callback(null, cached_files); 
    } else { 
    fs.readdir('.', function(err, files) { 
     if (!err) { 
     cached_files = files; 
     } 
     callback(err, files); 
    }); 
    } 
}; 

// main.js 
require('./db').init(function(err, files) { 
    // insert rest of your main.js code here 
}); 
+1

Las promesas no te ayudan con "múltiples resultados diferentes". Las promesas lo ayudan a sincronizar tanto los errores como los resultados adecuados cuando tiene múltiples resultados asincrónicos que otra cosa necesita para usar los resultados de. Así que editaría las promesas como parte de esa cita, porque no es correcto –

5

En general, es una muy mala idea tener cualquier estado en el módulo. Los módulos deben exponer funciones, no datos (sí, esto requiere cambiar un poco la estructura de su código). Simplemente pase las referencias a sus datos a las funciones de los módulos como parámetros.

(edición: acabo de dar cuenta que este es el enfoque de su último ejemplo Mi voto para él.)

módulo 1:

module.exports = function(params, callback) { ... } 

modulo2:

var createSomething = require('module1'); 
module.exports = function(params, callback) { 
    ... 
    var db = createSomething(params, function(err, res) { 
     ... 
     callback(err, res); 
    } 
} 

código principal:

var createSomethingOther = require('module2'); 
createSomethingOther(err, result) { 
    // do stuff 
} 
1

Por mi parte, este módulo es una función que toma devolución de llamada (y si está configurado internamente con promesas también devuelve promesa (ver https://github.com/medikoo/deferred));

El único problema con la devolución de llamada es que siempre se debe invocar en nextTick, por lo que incluso cuando llame a su función de módulo cuando se recopile toda la información, debe llamar a la devolución de llamada en el siguiente tic con el conjunto de resultados.

+0

Gracias. Entonces, inicialización perezosa? Por cierto, ¿por qué 'nextTick', qué convención? – elmigranto

+0

@elmigranto su módulo debe parecerse a la convención de la función asincrónica típica en Node.js, por lo que debe ser función, que tome la devolución de llamada como último argumento. Por convención, la devolución de llamada siempre se debe invocar en el próximo tic (nunca de forma inmediata). –

+0

Tengo su punto, lo que estoy preguntando es por qué la devolución de llamada siempre debe ser 'nextTick'ed incluso si se puede invocar inmediatamente. Además, ¿qué es "siempre" exactamente? ¿Es "en última instancia siempre" como en 'función de cosas (devolución de llamada) {async.parallel ([/ * ... * /], función realizada (err) {/ * proceso.nextTick (devolución de llamada); aquí a? * /}); } ' Si eso no es solo su propio punto de vista, una buena pregunta podría nacer. – elmigranto

Cuestiones relacionadas