bunnymq
Version:
BunnyMq is a RabbitMq wrapper
108 lines (91 loc) • 4.26 kB
JavaScript
const parsers = require('./message-parsers');
const utils = require('./utils');
const loggerAlias = 'bmq:consumer';
class Consumer {
constructor(connection) {
this._connection = connection;
this.channel = null;
}
set connection(value) {
this._connection = value;
}
get connection() {
return this._connection;
}
/**
* Get a function to execute on incoming messages to handle RPC
* @param {any} msg An amqp.node message object
* @param {string} queue The initial queue on which the handler received the message
* @return {function} a function to use in a chaining on incoming messages
*/
checkRpc(msg, queue) {
/**
* When message contains a replyTo property, we try to send the answer back
* @param {any} content the received message:
* @return {any} object, string, number... the current received message
*/
return (content) => {
if (msg.properties.replyTo) {
const options = { correlationId: msg.properties.correlationId, persistent: true, durable: true };
this._connection.config.transport.info(loggerAlias, `[${queue}][${msg.properties.replyTo}] >`, content);
this.channel.sendToQueue(msg.properties.replyTo, parsers.out(content, options), options);
}
return msg;
};
}
/**
* Create a durable queue on RabbitMQ and consumes messages from it - executing a callback function.
* Automaticaly answers with the callback response (can be a Promise)
* @param {string} queue The RabbitMQ queue name
* @param {object} options (Optional) Options for the queue (durable, persistent, etc.)
* @param {Function} callback Callback function executed when a message is received on the queue name, can return a promise
* @return {Promise} A promise that resolves when connection is established and consumer is ready
*/
/* eslint no-param-reassign: "off" */
consume(queue, options, callback) {
return this.subscribe(queue, options, callback);
}
subscribe(queue, options, callback) {
if (typeof options === 'function') {
callback = options;
// default message options
options = { persistent: true, durable: true };
}
// consumer gets a suffix if one is set on the configuration, to suffix all queues names
// ex: service-something with suffix :ci becomes service-suffix:ci etc.
const suffixedQueue = `${queue}${this._connection.config.consumerSuffix || ''}`;
return this._connection.get().then((channel) => {
this.channel = channel;
// when channel is closed, we want to be sure we recreate the queue ASAP so we trigger a reconnect by recreating the consumer
this.channel.addListener('close', () => {
this.subscribe(queue, options, callback);
});
return this.channel.assertQueue(suffixedQueue, options).then((q) => {
this._connection.config.transport.info(loggerAlias, 'init', q.queue);
this.channel.consume(q.queue, (msg) => {
this._connection.config.transport.info(loggerAlias, `[${q.queue}] < ${msg.content.toString()}`);
// main answer management chaining
// receive message, parse it, execute callback, check if should answer, ack/reject message
Promise.resolve(parsers.in(msg))
.then(body => callback(body, msg.properties))
.then(this.checkRpc(msg, q.queue))
.then(() => {
this.channel.ack(msg);
})
.catch((err) => {
// if something bad happened in the callback, reject the message so we can requeue it (or not)
this._connection.config.transport.error(loggerAlias, err);
this.channel.reject(msg, this._connection.config.requeue);
});
}, { noAck: false });
return true;
});
// in case of any error creating the channel, wait for some time and then try to reconnect again (to avoid overflow)
}).catch(() => utils.timeoutPromise(this._connection.config.timeout)
.then(() => this.subscribe(queue, options, callback)));
}
}
/* eslint no-unused-expressions: "off" */
/* eslint no-sequences: "off" */
/* eslint arrow-body-style: "off" */
module.exports = Consumer;