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

亚历山大·米尔斯

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

var foo = {};

module.exports = foo;

或者

function Foo(){}

module.exports = new Foo();

然而

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

var value = null;

function init(val){

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

  return value;
}

module.exports = init;

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

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

var instance = null;

module.exports = function init(value){

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

}

我遇到两个问题:

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

上面的模块初始化如下:

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

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

我认为没有必要创建解决此问题的模式的方法,但是也许我犯了错误的结构化代码结构,即创建了一个不需要存在的问题-初始化a一个值只有一次的模块,但是使用一个函数来初始化实例和检索实例。

jfriend00

听起来您正在尝试创建一个在某个位置进行初始化的模块,然后将该初始化中的某些共享资源用于该模块的其他用户。在现实世界中,这是一种半普遍的需求。

首先,理想的情况是模块可以加载或创建它所依赖的东西,因为这使它自己更具模块化和实用性,并且减轻了使用该模块的人的负担。因此,在您的情况下,如果您的模块仅能创建/加载首次创建该模块时所需的东西,并将该资源存储在其自己的模块变量中,那么这将是理想的情况。但是,这并非总是可能的,因为共享资源可能是其他人设置和初始化的责任,并且仅需要使该模块意识到这一点。

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

例如,您可以这样做:

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

然后,该模块的用户可以执行以下操作:

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

或这个:

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

注意:为使开发人员更加精明,有一些需要适当设置的方法(在可以正确使用之前),当调用任何需要进行设置的方法以正确通知时,检查是否已设置模块即可。开发人员,他们在正确设置模块之前已经调用了方法。否则,错误可能会在更远的下游发生,并且可能没有有用的错误消息。


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

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

如果您希望该模块的其他用户在初始化完成时得到通知,则可以允许他们自己注册一个回调,以便在模块准备就绪时得到通知。

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

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

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

异步初始化模块的用法:

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

加载模块并在其他人初始化它时得到通知的用法:

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

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

TypeScript和类似JS的单例模式

来自分类Dev

Ruby:开放模块的单例

来自分类Dev

是否需要将node.js模块包装在模块模式中?

来自分类Dev

C#单例模式

来自分类Dev

单例模式性能问题

来自分类Dev

单例模式-默认属性

来自分类Dev

模块模式真的必须是单例吗?

来自分类Dev

Node.js:单例变量会被覆盖对异步请求吗?

来自分类Dev

正确使用单例模式

来自分类Dev

从对象文字到模块模式的单例,代码组织的利弊

来自分类Dev

ndb单例模式

来自分类Dev

通过单例的node.js mySQL连接

来自分类Dev

node.js中模块模式的最佳实践

来自分类Dev

Typescript模块,需要外部node_modules

来自分类Dev

如何在node.js中将单例设计模式与mysql连接和池一起使用

来自分类Dev

实施单例模式时避免未解决的外部条件

来自分类Dev

XPath'contains()'需要单例(或空序列)

来自分类Dev

单例模式说明

来自分类Dev

在Node.js中与单例同步问题readdir

来自分类Dev

在理解单例模式方面需要帮助

来自分类Dev

设计模式:单例混淆

来自分类Dev

Node.js:单例变量是否被覆盖对异步请求起作用?

来自分类Dev

使用单例模式

来自分类Dev

node.js中模块模式的最佳实践

来自分类Dev

Node.js:请求模块是否支持无模式URI?

来自分类Dev

如何在node.js中将单例设计模式与mysql连接和池一起使用

来自分类Dev

单例设计模式

来自分类Dev

Android:扩展应用程序类。为什么我们需要实现单例模式?

来自分类Dev

实现单例模式