Node.js单例模块模式,需要外部输入

Ale*_*lls 2 javascript redis node.js socket.io

通常我们可以使用Node.js创建一个简单的单例对象:

var foo = {};

module.exports = foo;
Run Code Online (Sandbox Code Playgroud)

要么

function Foo(){}

module.exports = new Foo();
Run Code Online (Sandbox Code Playgroud)

然而

制作一个需要外部变量进行初始化的干净单例模块的​​最佳方法是什么?我最终做了这样的事情:

var value = null;

function init(val){

 if(value === null){
    value = val;
  }

  return value;
}

module.exports = init;
Run Code Online (Sandbox Code Playgroud)

这样,使用该模块的人可以传递某个变量的初始化值.另一种方法是这样:

function Baz(value){
this.value = value;
}

var instance = null;

module.exports = function init(value){

if(instance === null){
    instance = new Baz(value);
}
   return instance;

}
Run Code Online (Sandbox Code Playgroud)

我遇到了两个问题:

(1)这是次要的,但语义是错误的.我们可以将init重命名为getInstance,但我们不能将相同的函数文字意味着"初始化并获取",因为它们具有不同的含义.所以我们必须有一个能完成两件事的功能.创建实例并检索和实例.我不喜欢这个,特别是因为在某些情况下我们需要确保初始化实例的参数不为null.由于多个开发人员使用模块,目前还不清楚模块是否已经初始化,如果他们将未定义的模型传递给尚未初始化的模块,那么这可能会成为一个问题,或者至少会造成混淆.

(2)这一点更重要 - 在某些情况下,初始化Baz是异步的.例如,进行Redis连接或从文件读取以初始化单例或进行socket.io连接.这真是让我失望的原因.

例如,这里有一个我认为非常难看的模块,它存储了一个socket.io连接:

    var io = null;

    var init = function ($io) {

        if (io === null) {

            io = $io;

            io.on('connection', function (socket) {

                socket.on('disconnect', function () {

                });

            });
        }

        return io;
    };

module.exports = {
    getSocketIOConn: init
};
Run Code Online (Sandbox Code Playgroud)

上面的模块初始化如下:

var server = http.createServer(app);
var io = socketio.listen(server);
require('../controllers/socketio.js').getSocketIOConn(io);
Run Code Online (Sandbox Code Playgroud)

所以我正在寻找一种设计模式,它允许我们创建一个单例模块,初始化过程是异步的.理想情况下,我们不会在初始化实例和检索实例时具有相同的功能.这样的事情存在吗?

我认为不一定有办法创建一个解决这个问题的模式,但也许我错误地以一种创建不需要存在的问题的方式构造我的代码 - 初始化a的问题模块只有一次值,但使用一个函数来初始化实例并检索实例.

jfr*_*d00 7

听起来你正在尝试创建一个在一个地方初始化的模块,然后从该初始化为该模块的其他用户使用一些共享资源.这是现实世界中的半常见需求.

首先,它是理想的,如果一个模块可以加载或创建它所依赖的东西,因为这使它更加模块化并且有用,并且减轻了使用它的人的负担.因此,在您的情况下,如果您的模块可以在第一次创建模块时创建/加载它所​​需的东西,并且只将该资源存储在它自己的模块变量中,那么这将是理想的情况.但是,这并不总是可行的,因为共享资源可能是其他人负责设置和初始化,而这个模块只需要知道这一点.

因此,常见的方法是只为模块使用构造函数.在Javascript中,您可以允许构造函数采用提供初始化信息的可选参数.负责设置模块的代码将使用所需的setup参数调用构造函数.不负责设置模块的模块的其他用户可能只是不调用构造函数,或者如果他们想要返回值,或者他们应该传递其他构造函数参数,他们可以传递null该setup参数.

例如,您可以这样做:

var io;

module.exports = function(setup_io) {
    if (setup_io) {
        io = setup_io;
    }
    return module.exports;
};

module.exports.method1 = function() {
    if (!io) {
        throw new Error("Can't use method1 until io is properly initalized");
    }
    // code here for method1
};

// other methods here
Run Code Online (Sandbox Code Playgroud)

然后,模块的用户可以这样做:

// load myModule and initialize it with a shared variable
var myModule = require('myModule')(io);
Run Code Online (Sandbox Code Playgroud)

或这个:

// load myModule without initializing it 
// (assume some other module will initialize it properly)
var myModule = require('myModule');
Run Code Online (Sandbox Code Playgroud)

注意:对于开发人员的理智,如果需要进行适当的设置(在可以正确使用之前)来检查模块是否已经在调用需要该设置的任何方法时设置模块以便正确通知将是有用的他们在正确设置模块之前调用了方法的开发人员.否则,错误可能会发生在下游更远的地方,并且可能没有有用的错误消息.


如果您现在希望初始化过程是异步的,那也可以这样做,但它肯定会使模块的其他用途变得复杂,因为它们不一定知道模块何时/是否已初始化.

var moduleData;
var readyList = new EventEmitter();

module.exports = function(arg, callback) {
    // do some async operation here involving arg
    // when that operation completes, you stored the result
    // in local module data and call the callback
    readyList.on("ready", callback);
    someAsyncOperation(arg, function() {
        // set moduleData here
        // notify everyone else that the module is now ready
        readyList.emit("ready");
        // remove all listeners since this is a one-shot event
        readyList.removeAllListeners("ready");
    });
    return module.exports;
};
Run Code Online (Sandbox Code Playgroud)

如果您希望在初始化完成后通知此模块的其他用户,您可以允许他们自己注册回调以在模块准备就绪时收到通知.

// pass a callback to this method that will be called
// async when the module is ready
module.exports.ready = function(fn) {
    // if module already ready, then schedule the callback immediately
    if (moduleData) {
        setImmediate(fn);
    } else {
        readyList.on("ready", fn);
    }
};
Run Code Online (Sandbox Code Playgroud)

如果由于我不太了解的原因,您想要使用相同的构造函数进行初始化和就绪检测,那么可以这样做,尽管我认为它不像使用单独的方法进行就绪检测那样清晰:

var moduleData;
var readyList = new EventEmitter();

module.exports = function(arg, callback) {
    // if both arguments passed, assume this is a request for module
    // initialization
    if (arguments.length === 2) {
        // do some async operation here involving arg
        // when that operation completes, you stored the result
        // in local module data and call the callback
        readyList.on("ready", callback);
        someAsyncOperation(arg, function() {
            // set moduleData here
            // notify everyone else that the module is now ready
            readyList.emit("ready");
            // remove all listeners since this is a one-shot event
            readyList.removeAllListeners("ready");
        });
    } else {
        // constructor called just for a ready request
        // arg is the callback
        if (moduleData) {
            // if module already ready, then schedule the callback immediately
            setImmediate(arg);
        } else {
            // otherwise, save the callback
            readyList.on("ready", arg);
        }
    }
    return module.exports;
};
Run Code Online (Sandbox Code Playgroud)

异步初始化模块的用法:

// async initialization form
var myModule = require("myModule")(someArg, function() {
    // can use myModule here
});
Run Code Online (Sandbox Code Playgroud)

用于加载模块并在其他人初始化模块时收到通知:

var myModule = require("myModule")(function() {
    // can use myModule here
});
Run Code Online (Sandbox Code Playgroud)