Javascript以参数作为模块定义Singleton

时间:2019-01-03 00:59:07

标签: javascript

我试图在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) };

我希望能够像这样导入模块

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

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

我不确定是否可以这样做,因为我正在使用异步方法,并且在打印日志时数据还没有准备好。

2 个答案:

答案 0 :(得分:2)

这就是使用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();

如您从代码段中所见,构造函数将在您第一次调用getInstance时被调用。

然后,您应该能够导出getInstance方法并传递选项:

module.exports = Singl.getInstance;

答案 1 :(得分:1)

利用模块在所有模块上实现类似单例模​​式的方法是直接导出实例。

之所以可行,是因为require在第一次导入后缓存了导出,因此将在所有后续导入中返回该实例。

现在,您正在导出一个函数,尽管它始终是相同的函数,但它具有始终实例化类的新实例的功能,从而打破了您想要实现的单例模式约束(跨单个实例)模块)

由于要从外部指定单例实例化选项,因此可以在对代码进行最少更改的情况下执行此操作的一种方法是,使导出的函数返回实例(如果已存在),而不是实例化一个新实例:

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

这意味着所有执行require('dataService')(options)的文件都将接收相同的实例,并且首先应用模块导入哪个文件的是实例化选项。

请注意,所有后续调用仍必须采用require('dataService')()的形式(请注意额外的调用),这看起来像是一种代码气味,会使代码更难以理解。

为使代码更具可读性,我们可以添加一些详细说明:

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

使用方式如下:

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

另一步骤可能是通过在运行时告诉程序员是否错误地使用了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;

这不会使代码更具可读性,但会使其更安全。

如果我们将您的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);
}

单例中包含异步代码这一事实无关紧要。时间不是单例的属性,要求只有一个实例。

话虽如此,您可能需要考虑实际返回在方法中创建的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
})()
相关问题