Асинхронное / ожидание путаницы с помощью сингелтонов
Таким образом, независимо от того, что я прочитал, даже если я все сделаю правильно, я не могу понять, что такое асинхронность и ожидание. Например, у меня есть это в моем запуске.
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()
возврат не разрешится, пока все зависимые объекты не будут выполнены).