使用singeltons进行异步/等待混乱

时间:2018-10-30 13:19:55

标签: node.js rabbitmq node-amqplib

所以,无论我读什么书,即使我做对了,我也似乎无法摆脱异步和等待的困扰。例如,我在启动时就有这个。

startup.js

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

调试会跳至CommandBus的get实例的末尾(为rabbitmq启动一个通道),并启动Consumers.GetInstance(),因为channel为空,该调试失败。 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 :(得分:0)

您不能真正在构造函数中使用异步操作。问题在于,构造函数需要返回您的实例,因此它也不能返回将在完成时告诉调用方的promise。

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

通常的解决方法是创建一个工厂函数(在设计中可以是静态的),该函数返回一个可解析为新对象的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()需要调用这些工厂功能并将它们的Promise链接在一起,以便将返回的.init() Promise链接到另一个工厂函数也可以保证(因此,您的.init()返回的保证将无法解析,除非所有相关对象都已完成)。