简体   繁体   English

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

[英]Node.js singleton module pattern that requires external inputs

normally we might create a simple singleton object like so with Node.js: 通常我们可以使用Node.js创建一个简单的单例对象:

var foo = {};

module.exports = foo;

or 要么

function Foo(){}

module.exports = new Foo();

however 然而

what is the best way to make a clean singleton module that needs an external variable for initialization? 制作需要外部变量进行初始化的干净单例模块的​​最佳方法是什么? I end up making something like this: 我最终做出这样的事情:

var value = null;

function init(val){

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

  return value;
}

module.exports = init;

this way someone using the module can pass in an initializing value for a certain variable. 这样,使用模块的人就可以为某个变量传递一个初始化值。 Another way to do it would be like so: 另一种方法是这样的:

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

var instance = null;

module.exports = function init(value){

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

}

there's two problems I encounter with this: 我遇到两个问题:

(1) this is minor, but the semantics is wrong. (1)这是次要的,但语义是错误的。 We can rename init to getInstance, but we can't make the same function literal mean "initialize and get" since they are different meanings. 我们可以将init重命名为getInstance,但是我们不能使相同的函数字面意思是“初始化并获取”,因为它们的含义不同。 So we have to have a function that does two different things. 因此,我们必须有一个功能,可以执行两种不同的操作。 Create an instance and retrieve and instance. 创建一个实例并检索和实例。 I don't like this especially since in some cases we need to make sure the argument to initialize the instance is not null. 我特别不喜欢这样做,因为在某些情况下,我们需要确保用于初始化实例的参数不为null。 With multiple developers using a module it's not clear if a module has been initialized yet, and if they pass in undefined into the module that hasn't been initialized, that could become a problem or just confusing at the least. 在多个开发人员使用一个模块的情况下,尚不清楚一个模块是否已初始化,如果它们未定义地传递到尚未初始化的模块中,则可能会成为问题,或者至少会造成混乱。

(2) this is more important - in some cases initializing Baz is asynchronous. (2)这更重要-在某些情况下,初始化Baz是异步的。 For example, making a Redis connection or reading from a file to initialize a singleton, or making a socket.io connection. 例如,建立Redis连接或从文件读取以初始化单例,或建立socket.io连接。 This is what really trips me up. 这才是真正让我兴奋的东西。

eg here is a module that I have that I consider really ugly that stores a socket.io connection: 例如,这是一个我认为非常丑陋的模块,用于存储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
};

the above module is initialized like so: 上面的模块初始化如下:

var server = http.createServer(app);
var io = socketio.listen(server);
require('../controllers/socketio.js').getSocketIOConn(io);

So I am looking for a design pattern that allows us to create a singleton module where the initialization process is asynchronous. 因此,我正在寻找一种设计模式,该模式允许我们创建一个初始化过程为异步的单例模块。 Ideally we won't have the same function both initializing the instance as well as retrieving it. 理想情况下,初始化实例和检索实例都不会具有相同的功能。 Does such a thing exist? 这样的事情存在吗?

I don't think there is necessarily a way to create a pattern that solves this problem but perhaps I am making the mistake of structuring my code in a way that is creating a problem that doesn't need to exist- the problem of initializing a module with a value only once, but using one function to both init the instance and retrieve the instance. 我认为没有必要创建一种模式来解决此问题,但是我可能会犯这样的错误,即以一种创建不需要存在的问题的方式来构造我的代码-初始化a一个值只有一次的模块,但是使用一个函数来初始化实例和检索实例。

It sounds like you're trying to create a module that gets initialized in one place and then uses some shared resource from that initialization for other users of that module. 听起来您正在尝试创建一个在某个位置进行初始化的模块,然后将该初始化中的某些共享资源用于该模块的其他用户。 That is a semi-common need in the real world. 在现实世界中,这是一种半普遍的需求。

First off, it's ideal if a module can load or create the things that it depends on because that makes it more modular and useful on its own and puts less of a burden on someone using it. 首先,理想的情况是模块可以加载或创建它所依赖的东西,因为这使它自己更具模块化和实用性,并且减轻了使用它的人的负担。 So, in your case, if your module could just create/load the thing that it needs when the module is first created and just store that resource in it's own module variable, then that would be the ideal case. 因此,在您的情况下,如果您的模块可以在刚创建该模块时创建/加载它所​​需的内容,并将该资源存储在其自己的模块变量中,那么这将是理想的情况。 But, that is not always possible because the shared resource may be someone else's responsibility to set up and initialize and this module just needs to be made aware of that. 但是,这并非总是可能的,因为共享资源可能是其他人设置和初始化的责任,并且仅需要使该模块知道这一点。

So, the common way to do that is to just use a constructor function for the module. 因此,执行此操作的常用方法是仅对模块使用构造函数。 In Javascript, you can allow the constructor to take an optional argument that provides the initialization info. 在Javascript中,您可以允许构造函数采用一个可选参数来提供初始化信息。 The code responsible for setting up the module would call the constructor with the desired setup parameter. 负责设置模块的代码将使用所需的设置参数调用构造函数。 Other users of the module that weren't responsible for setting up the module could just either not call the constructor or if they want a return value or there are other constructor parameters that they should pass, they could pass null for that setup parameter. 不负责模块设置的模块的其他用户可能只是不调用构造函数,或者如果他们想要返回值,或者需要传递其他构造函数参数,则可以为该设置参数传递null

For example, you could do this: 例如,您可以这样做:

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

Then, users of the module could either do this: 然后,该模块的用户可以执行以下操作:

// load myModule and initialize it with a shared variable
var myModule = require('myModule')(io);

or this: 或这个:

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

Note: For developer sanity, it would be useful to have individual methods that require appropriate setup (before they can be used properly) to check to see if the module has been setup when any method is called that needs that setup in order to properly inform a developer that they have called a method before setting up the module properly. 注意:为了使开发人员更加明智,使用一些需要适当设置的方法(在可以正确使用之前)检查在调用任何需要进行设置以便正确通知的方法时是否已经设置了模块会很有用。开发人员,他们在正确设置模块之前已经调用了方法。 Otherwise, errors can happen much further downstream and likely won't have useful error messages. 否则,错误可能会在更下游发生,并且可能不会包含有用的错误消息。


If you now want the initialization process to be async, that can be done too, but it certainly complicates other uses of the module because they won't necessarily know when/if the module has been initialized. 如果现在希望初始化过程是异步的,也可以这样做,但是它肯定会使模块的其他用途复杂化,因为它们不一定知道何时/是否已初始化模块。

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;
};

If you have other users of this module that wish to be notified when it has finished initializing, you can allow them to register a callback themselves to be notified when the module is ready. 如果您希望该模块的其他用户在初始化完成时得到通知,则可以允许他们自己注册一个回调,以便在模块准备就绪时得到通知。

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

If, for reasons I don't quite understand, you want to use the same constructor for both initialization and ready detection, that can be done, though I don't think it's near as clear as just using a separate method for ready detection: 如果出于某种原因(我不太了解),您想要使用相同的构造函数进行初始化和就绪检测,那么可以这样做,尽管我认为它不像使用单独的方法进行就绪检测那样清晰:

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;
};

Usage for async initializing the module: 异步初始化模块的用法:

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

Usage for loading the module and getting notified when someone else has initialized it: 加载模块并在其他人初始化它时得到通知的用法:

var myModule = require("myModule")(function() {
    // can use myModule here
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM