我试图在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);
我不确定是否可以这样做,因为我正在使用异步方法,并且在打印日志时数据还没有准备好。
答案 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
})()