2012-08-06 95 views
21

我想以異步方式初始化模塊,並提出幾個想法。我需要包含來自Mongo和其他數據的收藏列表的數據庫對象,但爲了簡潔起見,./中的文件列表將盡其所能。Node.js模塊的異步初始化

我無法導出函數或類,因爲我需要require('db')每次都返回相同的對象。


首先也是最簡單的東西來到我的腦海裏是分配給module.exportsObject後來填充它:

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

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

壞事 - 我真的不從外部知道名單是準備並沒有好方法來檢查錯誤。


的路我COMED了將繼承EventEmitter,並通知大家,DB是準備或發生錯誤。如果一切正常 - 繼續前進。

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

而且現在我認爲這是比較合理的:

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

我應該怎麼挑,爲什麼?一般來說這種想法有多糟糕,特別是我的選擇?

感謝您的反饋意見。

回答

27

TL; DR:如果您只是計劃在啓動時讀取本地文件,請使用readdirSync()而不是readdir()。如果您打算實際從遠程數據庫讀取數據或在運行時執行任何I/O操作,請使用選項#2 - 回調。下面的解釋和代碼示例。

詳細說明:

雖然起初,這似乎是一個模塊/ dependecy /需要相關的問題,它真的不是。這是一個如何處理異步代碼的通用問題。讓我解釋一下:

require()基本上是唯一的同步函數廣泛用於處理I/O(它需要從文件系統的其他模塊)節點。同步意味着它實際上將其數據作爲返回值返回,而不是調用回調。

最基本的101規則在異步編程:

您可以從未採取異步的代碼,併爲它創建一個同步API。

require採用了特殊的同步版本的readFile稱爲readFileSync。由於模塊實際上只在程序開始時加載,所以它在讀取模塊時阻止node.js執行的事實不成問題。

但在您的示例中,您嘗試執行額外的異步I/O - readdir()在需求階段完成。因此,您需要使用同步版本的此命令或API需要更改...

因此,存在您的問題的背景。

您確定的兩個基本選項:

  1. 使用使用回調一個承諾(這是基本相同的EventEmitter爲例)
  2. (你的第二個例子顯示這口井) 第三個是:
  3. 使用同步版本的readdir()命令調用readdirSync()

我會用選項#3爲了簡單的原因 - 但只有當你打算在啓動時剛讀一對夫婦的文件,你的榜樣暗示。如果以後你的數據庫模塊實際上將連接到數據庫 - 或者如果你打算在運行時執行這些操作,那麼現在就跳船並使用異步API。

沒有多少人記得這一點,但承諾實際上是如何處理node.js中異步的原始默認值。在節點0.1中。30然而,通過function(err, result)簽名的標準化回調,已成爲預期。這主要是出於簡單的原因。

如今,絕大多數異步調用都將此標準回調作爲最後一個參數。你的數據庫驅動程序做到了,你的web框架就可以做到 - 它無處不在。你應該保持流行的設計並使用它。

喜歡承諾或事件的唯一原因是,如果你有多個不同的結果可能發生。例如,可以打開一個插座,接收數據,關閉,沖洗等。

這不是你的情況。你的模塊總是執行相同的操作(讀取一些文件)。所以選項#2它是(除非你可以留同步)。

最後,這裏有兩個獲獎方案重寫咯:

同步選項:只是在啓動時本地文件系統

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

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

異步選項:
當你想使用數據庫等

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

承諾不會幫助你「多個不同的結果」。如果有多個異步結果需要其他結果使用,那麼Promises可以幫助您同步錯誤和正確的結果。所以我會編輯引用中的承諾部分,因爲它不正確 – 2013-08-10 21:02:45

5

一般來說,在模塊中有任何狀態是非常糟糕的主意。模塊應該公開函數,而不是數據(是的,這需要稍微改變你的代碼結構)。只需將對數據的引用傳遞給作爲參數的模塊函數。

編輯:剛剛意識到這是你最後的實例方法我爲它投票。)

模塊1:

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

模塊2:

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

主要代碼:

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

在我這邊,這個模塊是一個需要回調的函數(如果在內部使用promises進行配置也會返回promise(請參見https://github.com/medikoo/deferred));

回調的唯一問題是,按照慣例,它總是應該在nextTick中調用,所以即使您在收集所有數據時調用模塊函數,仍應在下一個結果集中使用回調調用您的回調函數。

+0

謝謝。那麼,懶惰的初始化?順便說一句,爲什麼'nextTick',什麼約定? – elmigranto 2012-08-07 13:33:56

+0

@elmigranto您的模塊應該類似於Node.js中典型異步函數的約定,所以它應該是函數,它將回調作爲最後一個參數。按照慣例,回調應該總是在下一個勾號中調用(從不立即)。 – 2012-08-07 15:58:24

+0

我已經明白了你的觀點,我問的是爲什麼回調應該總是'nextTick'ed,即使它可以被立即調用。另外,究竟什麼是「總是」?它是「最終總是」像在函數的東西(回調){async.parallel([/ * ... * /],功能完成(錯誤){/ * process.nextTick(回調);這裏?* /}); }'。如果那不僅僅是你自己的POV,那麼可能會誕生一個很好的問題。 – elmigranto 2012-08-07 16:16:13