Асинхронное / ожидание путаницы с помощью сингелтонов

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

startup.js

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

Отладка переходит к концу экземпляра get для CommandBus (запускает канал для rabbitmq) и запускает Consumers.GetInstance(), который завершается сбоем, так как channel имеет значение 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 ответ

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

Итак, в вашем 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() возврат не разрешится, пока все зависимые объекты не будут выполнены).

Другие вопросы по тегам