简体   繁体   English

Javascript以参数作为模块定义Singleton

[英]Javascript define Singleton with arguments as a Module

I am trying to define a Singleton in Javascript to be able to consume from different files. 我试图在Javascript中定义一个Singleton,以便能够从其他文件中使用。

class DataService {

  constructor(options) {
    this.models = options.models ;
    this.data = {};
    this.refresh();
  }

  refresh() {
    Promise.all([
      this.models.DATA.model.findAll({
        raw: true,
        attributes: ['key', 'value']
      })
    ]).then(([data]) => {
      this.data = this.parseData(data);
    });
  }

  parseData(data) {
    data.map(x => {
      this.data[x.key] = JSON.parse(x.value);
    });
    return this.data;
  }

}

module.exports = (options) => { return new DataService(options) };

I want to be able to import the module like this 我希望能够像这样导入模块

const data = require('dataService')(options);

console.log('data.example', data.example);

I am not sure if it is possible to do this, since I am using async methods, and the data is not ready when I print the log. 我不确定是否可以执行此操作,因为我使用的是异步方法,并且在打印日志时数据尚未准备就绪。

The way in which you can leverage modules to achieve a singleton-like pattern across all modules is to export an instance directly. 可以利用模块在所有模块上实现类似单例模​​式的方法是直接导出实例。

The reason this works is that require caches the exports after the first import and thus will return that instance on all subsequent imports. 之所以起作用,是因为require在第一次导入后缓存导出,因此将在所有后续导入中返回该实例。

Right now you're exporting a function which, although it will always be the same function, has capabilities to always instantiate a new instance of your class and thus is breaking the singleton-pattern constraint you want to achieve (single instance across modules) 现在,您正在导出一个函数,尽管该函数将始终是相同的函数,但具有始终实例化类的新实例的功能,从而打破了您想要实现的单例模式约束(跨模块的单个实例)

Because you want to externally specify the singleton instantiation options, one way you can do this with minimal changes to your code is to have the exported function return an instance if it already exists, rather than instantiate a new one: 因为您想从外部指定单例实例化选项,所以可以在对代码进行最少更改的情况下执行此操作的一种方法是,使导出的函数返回一个已经存在的实例,而不是实例化一个新实例:

let instance; // all files will receive this instance
module.exports = (options) => {
  if (!instance) {
    // only the first call to require will use these options to create an instance
    instance = new DataService(options);
  } 
  return instance;
}

This means that all files that do require('dataService')(options) will receive the same instance and whichever file imports the module first is who's instantiation options will apply. 这意味着所有require('dataService')(options)都将接收相同的实例,并且无论哪个模块首先导入,将应用谁的实例化选项。

Do note that all subsequent calls will still have to be of the form require('dataService')() (notice the extra invocation) which seems like a code-smell and would make the code harder to understand. 请注意,所有后续调用仍必须采用require('dataService')() (注意额外的调用),这看起来像是代码的气味,会使代码更难于理解。

To make the code more readable, we could add some verboseness: 为了使代码更具可读性,我们可以添加一些详细信息:

let instance; // all files will receive this instance
module.exports = {
  getInstance(options) {
    if (!instance) {
      // only the first call to getInstance will use these options to create an instance
      instance = new DataService(options);
    } 
    return instance;
  }
}

Which would be used like: 可以这样使用:

const instance = require('dataService').getInstance(options);
const instance = require('dataService').getInstance();
const instance = require('dataService').getInstance();    

Another step could be to make the code more resilient to abuse by telling the programmer at run-time if they are using the API wrongly: 另一个步骤可能是通过在运行时告诉程序员是否错误地使用了API,从而使代码更易于滥用:

if (!instance) {
  instance = new DataService(options);
} else if (options) {
  // throw error on all subsequent calls with an options arg `require('dataService')(options)`
  throw Error('Instance is already instantiate with `options`')
}
return instance;

This won't make the code more readable but would make it a bit safer. 这不会使代码更具可读性,但是会使其更安全。

If we interpret your API to mean "anytime options are passed, we should instantiate a new singleton", then you can consider maintaining a collection of instances instead, retrievable by some id (or maybe even the memory reference of the options themselves): 如果我们将您的API解释为“随时传递选项,我们应该实例化一个新的单例”,那么您可以考虑维护实例的集合,而可以通过某些ID(甚至可能是选项本身的内存引用)进行检索:

let instances = new Map();
module.exports = (options) => {
  if (!instances.has(options.id)) {
    instances.set(options.id, new DataService(options));
  }
  return instances.get(options.id);
}

The fact that you have async code in your singleton shouldn't matter. 单例中包含异步代码这一事实无关紧要。 Time is not a property of a singleton, the requirement is to only have a single instance. 时间不是单例的属性,要求只有一个实例。

That being said, you might want to consider actually returning the promises created in your methods so that you can properly chain them or await on them: 话虽如此,您可能需要考虑实际返回在方法中创建的promise,以便可以正确地将它们链接或等待它们:

class DataService {

  constructor(options) {
    this.models = options.models ;
    this.data = {};
    this.refresh();
  }

  refresh() {
    return Promise.all(/* ... */).then(/* ... */);
  //^^^^^^ return the promise chain so we can react to it externally
  }

  // ...

}

(async () => {
  await new DataService().refresh(); // now you can do this
})()

That's how you can implement the Singleton with ES6: 这样便可以在ES6中实现Singleton:

 class Singl { constructor(options) { console.log('calling constructor'); } static getInstance(options) { if (!Singl.instance) { Singl.instance = new Singl(options); } return Singl.instance; } } // the constructor will be called only once Singl.getInstance(); Singl.getInstance(); Singl.getInstance(); 

As you can see from the snippet, the constructor will be called just the first time you call getInstance . 从片段中可以看到,构造函数将在您第一次调用getInstance时被调用。

Then you should be able to export the getInstance method and pass options: 然后,您应该能够导出getInstance方法并传递选项:

module.exports = Singl.getInstance;

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

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