Javascript определяет Singleton с аргументами в качестве модуля - PullRequest
0 голосов
/ 03 января 2019

Я пытаюсь определить синглтон в Javascript для возможности использования из разных файлов.

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 голосов
/ 03 января 2019

Вот как вы можете реализовать Singleton с ES6:

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;
0 голосов
/ 03 января 2019

Способ использования модулей для получения одноэлементного шаблона для всех модулей - это прямой экспорт экземпляра.

Причина, по которой это работает, заключается в том, что 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
})()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...