Асинхронное / ожидание путаницы с помощью сингелтонов - PullRequest
0 голосов
/ 30 октября 2018

Так что, независимо от того, что я прочитал, даже если я все сделаю правильно, я не могу понять навык асинхронности и ожидания. Например, у меня есть это в моем запуске.

startup.js

  await CommandBus.GetInstance();
  await Consumers.GetInstance();

Отладка переходит в конец экземпляра get для CommandBus (запускает канал для rabbitmq) и запускает Consumers.GetInstance (), которая завершается сбоем, поскольку канал имеет значение null. CommandBus.js

export default class CommandBus {
  private static instance: CommandBus;
  private channel: any;
  private conn: Connection;

  private constructor() {
    this.init();
  }

  private async init() {
    //Create connection to rabbitmq
    console.log("Starting connection to rabbit.");
    this.conn = await connect({
      protocol: "amqp",
      hostname: settings.RabbitIP,
      port: settings.RabbitPort,
      username: settings.RabbitUser,
      password: settings.RabbitPwd,
      vhost: "/"
    });

    console.log("connecting channel.");
    this.channel = await this.conn.createChannel();
  }

  static async GetInstance(): Promise<CommandBus> {
    if (!CommandBus.instance) {
      CommandBus.instance = new CommandBus();
    }

    return CommandBus.instance;
  }
  public async AddConsumer(queue: Queues) {
    await this.channel.assertQueue(queue);
    this.channel.consume(queue, msg => {
      this.Handle(msg, queue);
    });
  }
}

Consumers.js

export default class Consumers {
  private cb: CommandBus;
  private static instance: Consumers;

  private constructor() {
    this.init();
  }

  private async init() {
    this.cb = await CommandBus.GetInstance();
    await cb.AddConsumer(Queues.AuthResponseLogin);
  }

  static async GetInstance(): Promise<Consumers> {
    if (!Consumers.instance) {
      Consumers.instance = new Consumers();
    }

    return Consumers.instance;
  }
}

Извините, я понимаю, что это в Typescript, но я думаю, это не имеет значения. Проблема возникает именно при вызове cb.AddConsumer, который можно найти CommandBus.js. Он пытается установить очередь на канале, который еще не существует. То, что я не понимаю, смотрит на это. Я чувствую, что охватил все области ожидания, так что он должен ждать создания канала. CommandBus всегда выбирается как одиночный. Я не знаю, если это создает проблемы, но опять же, это одна из тех областей, которые я также освещаю. Спасибо за любую помощь, спасибо всем.

1 Ответ

0 голосов
/ 30 октября 2018

Вы не можете использовать асинхронные операции в конструкторе. Проблема в том, что конструктор должен вернуть ваш экземпляр, поэтому он также не может вернуть обещание, которое сообщит вызывающей стороне, когда это будет сделано.

Итак, в вашем Consumers классе await new Consumers(); не делает ничего полезного. new Consumers() возвращает новый экземпляр Consumers объекта, поэтому, когда вы await, он фактически ничего не ждет. Помните, что await делает с вами что-то полезное await обещание. У него нет особых полномочий ждать завершения работы вашего конструктора.

Обычным способом решения этой проблемы является создание фабричной функции (которая может быть статической в ​​вашем дизайне), которая возвращает обещание, которое разрешается для нового объекта.

Поскольку вы также пытаетесь создать синглтон, вы должны кэшировать обещание при первом его создании и всегда возвращать обещание вызывающей стороне, чтобы вызывающий всегда использовал .then() для получения готового экземпляра. Когда они в первый раз позвонят, они получат обещание, которое еще не выполнено, но позже они получат обещание, которое уже выполнено. В любом случае они просто используют .then(), чтобы получить экземпляр.

Я недостаточно хорошо знаю TypeScript, чтобы предложить вам реальный код для этого, но, надеюсь, вы поняли идею из описания. Превратите GetInstance() в фабричную функцию, которая возвращает обещание (которое вы кэшируете) и которое разрешает это обещание для вашего экземпляра.

Примерно так:

static async GetInstance(): Promise<Consumers> {
  if (!Consumers.promise) {
      let obj = new Consumers();
      Consumers.promise = obj.init().then(() => obj);
  }
  return Consumers.promise;
}

Тогда вызывающий абонент сделает:

Consumers.getInstance().then(consumer => {
    // code here to use the singleton consumer object
}).catch(err => {
    console.log("failed to get consumer object");
});

Вам придется делать то же самое в любом классе, в котором асинхронные операции также задействованы при инициализации объекта (например, CommandBus), и каждый вызов .init() должен вызывать базовый класс super.init().then(...), чтобы базовый класс мог делать свое дело чтобы правильно инициализироваться, и обещание, что ваш .init() также связано с базовым классом. Или, если вы создаете другие объекты, которые сами имеют фабричные функции, тогда ваш .init() должен вызвать эти фабричные функции и связать их обещания вместе, чтобы возвращаемое обещание .init() связывалось также с другими обещаниями фабричной функции ( поэтому обещание, которое ваши .init() вернут, не разрешится, пока все зависимые объекты не будут выполнены).

...