2012-08-06 12 views
21

Vorrei inizializzare il modulo in modo asincrono e proporre un paio di idee. Ho bisogno di un oggetto DB con l'elenco delle raccolte da Mongo e altri dati, ma l'elenco dei file in ./ farà per brevità.Inizializzazione asincrona del modulo Node.js

Non riesco a esportare la funzione o la classe perché ho bisogno di require('db') per restituire sempre lo stesso oggetto.


Prima e più semplice che cosa è venuto in mente è quello di assegnare module.exports-Object e popolare in un secondo momento:

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

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

Brutta cosa - io non so davvero dall'esterno quando la lista è pronta e non è un buon modo per controllare gli errori.


Seconda modo ho Comed in mente è di ereditare EventEmitter e notificare a tutti che DB è pronto o errore. Se tutto ok, continua.

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(); 

E ora penso che sia più ragionevole:

// 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! 
}); 

Cosa devo scegliere e perché? Quanto è grave questa idea in generale e le mie opzioni in particolare?

Grazie per il feedback.

risposta

27

TL; DR: Uso readdirSync() invece di readdir() se si sta solo pensando di leggere file locali in fase di avvio. Se hai intenzione di leggere i dati dal database remoto o eseguire qualsiasi I/O in fase di esecuzione, utilizza l'opzione # 2 - il callback. Spiegazione e esempi di codice di seguito.

spiegazione dettagliata:

Mentre in un primo momento questo potrebbe sembrare un/dependecy/domanda richiederà legati modulo, non è davvero. È una questione generica su come gestire il codice asincrono . Mi spiego:

require() è fondamentalmente l'unica funzione sincrona ampiamente utilizzato in tutto il nodo che si occupa di I/O (che richiede altri moduli da file system). Sincrono significa che in realtà restituisce i dati come valore di ritorno, invece di chiamare un callback.

Il più semplice 101 regola nella programmazione asincrona è:

È possibile mai prendere un pezzo asincrona di codice e creare un'API sincrono per esso.

require utilizza una speciale versione sincrona di readFile chiamato readFileSync. Dal momento che i moduli vengono caricati solo all'inizio del programma, il fatto che blocchi l'esecuzione di node.js mentre sta leggendo il modulo non è un problema.

Nell'esempio, tuttavia, si tenta di eseguire ulteriori I/O asincroni - readdir() eseguiti durante la fase di richiesta. Pertanto, è necessario utilizzare la versione sincrona sincrona o l'API deve cambiare ...

Quindi, c'è lo sfondo del problema.

identificato le due opzioni di base:

  1. usando un promessa (che è essenzialmente la stessa del vostro EventEmitter esempio)
  2. utilizzando un callback (il secondo esempio mostra bene questo) e un terzo è:
  3. utilizzando una versione sincrona del comando readdir() denominata readdirSync()

Vorrei utilizzare il opzione # 3 per ragioni di semplicità - ma solo se avete intenzione di leggere solo un paio di file in fase di avvio, come il tuo esempio implica. Se in seguito il tuo modulo DB si collegherà effettivamente a un database - o se hai intenzione di fare tutto ciò in fase di runtime, salta subito la barca e vai con API asincrone.

Non molte persone ricordano questo, ma le promesse erano in realtà il default originale di come gestire async in node.js. Nel nodo 0.1.30 tuttavia erano removed and replaced promesse da un callback standardizzato con la firma function(err, result). Questo è stato fatto in gran parte per motivi di semplicità.

In questi giorni, la stragrande maggioranza delle chiamate asincrone accetta questo callback standard come ultimo parametro. Il tuo driver di database lo fa, il tuo framework web lo fa - è ovunque. Dovresti stare con il design prevalente e usarlo anche tu.

L'unica ragione per preferire promesse o eventi è se si dispone di più risultati diversi che può accadere. Per esempio una presa può essere aperto, ricevere dati, essere chiusi, arrossato ecc

Questo non è il vostro caso. Il tuo modulo fa sempre la stessa cosa (legge alcuni file). Quindi l'opzione numero numero 2 (a meno che non sia possibile rimanere sincrono).

Infine, ecco le due opzioni vincenti riscritto un po ':

opzione sincrono:
buono solo per il filesystem locale al momento dell'avvio

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

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

opzione asincrono:
per quando si desidera utilizzare DB ecc.

// 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

Le promesse non ti aiutano con "più risultati diversi". Le promesse aiutano a sincronizzare sia gli errori che i risultati corretti quando si hanno più risultati asincroni che qualcos'altro ha bisogno di usare i risultati di. Quindi modificherei la parte promesse da quella citazione, perché non è corretta –

5

In generale è una pessima idea avere uno stato nel modulo. I moduli dovrebbero esporre funzioni, non dati (sì, questo richiede di cambiare un po 'la struttura del codice). Basta passare i riferimenti ai tuoi dati a funzioni di moduli come parametri.

(edit: appena realizzato che questo è l'approccio dal tuo ultimo esempio il mio voto per esso.)

Module1:

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

Module2:

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

codice principale:

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

Da parte mia, tale modulo è una funzione che accetta la richiamata (e se è configurata internamente con promesse restituisce anche promessa (vedere https://github.com/medikoo/deferred));

L'unico problema con callback è che per convenzione è sempre dovrebbe essere invocato nextTick, quindi, anche quando si chiama la funzione del modulo quando tutti i dati sono raccolti si dovrebbe comunque chiamare la richiamata nel prossimo zecca con set di risultati.

+0

Grazie. Quindi, inizializzazione pigra? A proposito, perché 'nextTick', quale convenzione? – elmigranto

+0

@elmigranto il tuo modulo dovrebbe assomigliare alla convenzione della tipica funzione asincrona in Node.js, quindi dovrebbe essere la funzione, che accetta la richiamata come ultimo argomento. Per convenzione la callback dovrebbe sempre essere richiamata al prossimo tick (mai immediatamente). –

+0

Ho il tuo punto, quello che sto chiedendo è perché callback dovrebbe sempre essere 'nextTick'ed anche se può essere richiamato immediatamente. Inoltre, cosa è "sempre" esattamente? È "alla fine sempre" come in 'function stuff (callback) {async.parallel ([/ * ... * /], funzione done (err) {/ * process.nextTick (callback); qui a? * /}); } '. Se questo non è solo il tuo punto di vista, potrebbe nascere una buona domanda. – elmigranto

Problemi correlati