【问题标题】:Async/await confusion using singeltons使用单例的异步/等待混淆
【发布时间】:2018-10-30 22:19:06
【问题描述】:

所以无论我读过什么,即使我做对了,我似乎也无法掌握异步和等待的窍门。例如,我的创业公司就有这个。

startup.js

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

调试跳转到 CommandBus 的 get 实例的末尾(为 rabbitmq 启动一个通道)并启动 Consumers.GetInstance(),但由于通道为空而失败。 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 始终作为单例获取。如果这会引起问题,我不会,但这也是我在等待中涵盖的领域之一。任何帮助都非常感谢大家。

【问题讨论】:

  • 这毫无意义:return await CommandBus.instance - 因为它不是函数调用,await 仅适用于函数调用
  • @slebetman 哎呀,我认为那是来自绝望的地方。更新了代码以反映更改。
  • 你在awaiting 一个非异步的类构造函数。无法完成异步构造函数
  • @Wainage 谢谢。我已经清理并编辑了问题中的代码。

标签: node.js rabbitmq node-amqplib


【解决方案1】:

您不能在构造函数中真正使用异步操作。问题是构造函数需要返回你的实例,所以它不能也返回一个承诺,告诉调用者什么时候完成。

因此,在您的 Consumers 课程中,await new Consumers(); 没有做任何有用的事情。 new Consumers() 返回一个 Consumers 对象的新实例,所以当你 await 它实际上并没有等待任何东西。请记住,await 对你做了一些有用的事情await 一个承诺。它没有任何特殊的权力来等待你的构造函数完成。

解决这个问题的常用方法是创建一个工厂函数(在您的设计中可以是静态的),它返回一个解析为新对象的承诺。

由于您还尝试创建单例,因此您将在第一次创建 Promise 时对其进行缓存,并始终将 Promise 返回给调用者,因此调用者将始终使用 .then() 来获取完成的实例。他们第一次调用它时,他们会得到一个尚未完成的承诺,但后来他们会得到一个已经履行的承诺。无论哪种情况,他们都只是使用.then() 来获取实例。

我不太了解 TypeScript,无法向您推荐执行此操作的实际代码,但希望您能从描述中获得想法。将 GetInstance() 转换为工厂函数,该函数返回一个 Promise(您缓存的)并让该 Promise 解析到您的实例。

类似这样的:

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() 返回的承诺不会得到解决。

【讨论】:

  • 什么 super.init 会被调用?在您的示例中调用它不会涵盖它的唯一出现吗?
  • @JoshuadeLeon - 我并没有真正理解 CommandBus 和消费者之间的关系。关键是,如果您使用工厂函数创建 Consumers 单例,则需要确保创建的任何其他异步对象也已完全创建,并且应将 .init() 承诺链接到该对象。如果您从 CommandBus 继承,那么您将使用 super.init().then(...)。如果您正在调用工厂函数来创建 CommandBus 对象,那么您将使用该 CommandBus.getInstance().then(...) 来代替。
猜你喜欢
  • 2015-11-06
  • 2019-01-23
  • 1970-01-01
  • 2020-09-14
  • 2020-05-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-14
相关资源
最近更新 更多