所以,无论我读什么书,即使我做对了,我也似乎无法摆脱异步和等待的困扰。例如,我在启动时就有这个。
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始终以单例形式获取。我不认为这会带来问题,但同样,这也是我要讨论的领域之一。任何帮助都很好,谢谢大家。
答案 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()
返回的保证将无法解析,除非所有相关对象都已完成)。