3

I'm trying to do something that feels like it should be straightforward, but is proving surprisingly difficult.

I have a function to subscribe to a RabbitMQ queue. Concretely, this is the Channel.consume function here: http://www.squaremobius.net/amqp.node/channel_api.html#channel_consume

It returns a promise which is resolved with a subscription id - which is needed to unsubscribe later - and also has a callback argument to invoke when messages are pulled off the queue.

When I want to unsubscribe from the queue, I'd need to cancel the consumer using the Channel.cancel function here: http://www.squaremobius.net/amqp.node/channel_api.html#channel_cancel. This takes the previously returned subscription id.

I want to wrap all of this stuff in an Observable that subscribes to the queue when the observable is subscribed to, and cancels the subscription when the observable is unsubscribed from. However, this is proving somewhat hard due to the 'double-asynchronous' nature of the calls (I mean to say that they have both a callback AND return a promise).

Ideally, the code I'd like to be able to write is:

return new Rx.Observable(async (subscriber) => {
  var consumeResult = await channel.consume(queueName, (message) => subscriber.next(message));
  return async () => {
    await channel.cancel(consumeResult.consumerTag);
  };
});

However, this isn't possible as this constructor doesn't support async subscriber functions or teardown logic.

I've not been able to figure this one out. Am I missing something here? Why is this so hard?

Cheers, Alex

cartant
  • 57,105
  • 17
  • 163
  • 197
AlexC
  • 1,646
  • 1
  • 14
  • 26

1 Answers1

3

The created observable does not need to wait for the channel.consume promise to resolve, as the observer (it's an observer that's passed, not a subscriber) is only called from within the function you provide.

However, the unsubscribe function that you return will have to wait for that promise to resolve. And it can do that internally, like this:

return new Rx.Observable((observer) => {
  var consumeResult = channel.consume(queueName, (message) => observer.next(message));
  return () => {
    consumeResult.then(() => channel.cancel(consumeResult.consumerTag));
  };
});
cartant
  • 57,105
  • 17
  • 163
  • 197
  • Thanks for your response. I had also considered your suggestion prior to asking, but my issue with it is that nothing is waiting for the channel.cancel promise to resolve. So, let's say the call to channel.cancel only resolves after 3 seconds. There is a possibility that new messages will be received on that channel, but the Rx observer will have already been unsubscribed and so these messages will be lost into the ether. This is what I'm trying to avoid. Do you have any suggestion to get around this issue? – AlexC Apr 06 '17 at 06:49
  • I don't see how that's a problem. According to the *Subscribing and Unsubscribing* section of the [observable contract](http://reactivex.io/documentation/contract.html): *When an observer issues an Unsubscribe notification to an Observable, the Observable will attempt to stop issuing notifications to the observer. It is not guaranteed, however, that the Observable will issue no notifications to the observer after an observer issues it an Unsubscribe notification.* – cartant Apr 06 '17 at 07:06
  • So if the channel keeps pumping out messages until the cancel resolves, the observer should receive them. That is, the observer implementation should not be expecting no further messages to be emitted. – cartant Apr 06 '17 at 07:06
  • Interesting. Thanks for pointing that out, I guess this was the point I was missing! – AlexC Apr 06 '17 at 09:26