Способ использования модулей для получения одноэлементного шаблона для всех модулей - это прямой экспорт экземпляра.
Причина, по которой это работает, заключается в том, что 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 как «каждый раз, когда передаются опции, мы должны создать новый синглтон», тогда вы можете вместо этого поддерживать коллекцию экземпляров, которую можно извлечь по некоторому идентификатору (или, возможно, даже по ссылке в памяти самих опций ):
let instances = new Map();
module.exports = (options) => {
if (!instances.has(options.id)) {
instances.set(options.id) = new DataService(options);
}
return instances.get(options.id);
}
Тот факт, что у вас есть асинхронный код в вашем синглтоне, не должен иметь значения. Время не является свойством одиночного, требуется только один экземпляр.
При этом вы, возможно, захотите рассмотреть фактическое возвращение обещаний, созданных в ваших методах, чтобы вы могли должным образом связать их в цепочку или ожидать от них:
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
})()