nsqjs
Version:
NodeJS client for NSQ
196 lines (171 loc) • 4.79 kB
JavaScript
const {EventEmitter} = require('events')
const wire = require('./wire')
/**
* Message - a high-level message object, which exposes stateful methods
* for responding to nsqd (FIN, REQ, TOUCH, etc.) as well as metadata
* such as attempts and timestamp.
* @type {Message}
*/
class Message extends EventEmitter {
// Event types
static get BACKOFF() {
return 'backoff'
}
static get RESPOND() {
return 'respond'
}
// Response types
static get FINISH() {
return 0
}
static get REQUEUE() {
return 1
}
static get TOUCH() {
return 2
}
/**
* Instantiates a new instance of a Message.
* @constructor
* @param {String} id
* @param {String|Number} timestamp
* @param {Number} attempts
* @param {String} body
* @param {Number} requeueDelay
* @param {Number} msgTimeout
* @param {Number} maxMsgTimeout
*/
constructor(rawMessage, requeueDelay, msgTimeout, maxMsgTimeout) {
super(...arguments) // eslint-disable-line prefer-rest-params
this.rawMessage = rawMessage
this.requeueDelay = requeueDelay
this.msgTimeout = msgTimeout
this.maxMsgTimeout = maxMsgTimeout
this.hasResponded = false
this.receivedOn = Date.now()
this.lastTouched = this.receivedOn
this.touchCount = 0
this.trackTimeoutId = null
// Keep track of when this message actually times out.
this.timedOut = false
this.trackTimeout()
}
get id() {
return wire.unpackMessageId(this.rawMessage)
}
get timestamp() {
return wire.unpackMessageTimestamp(this.rawMessage)
}
get attempts() {
return wire.unpackMessageAttempts(this.rawMessage)
}
get body() {
return wire.unpackMessageBody(this.rawMessage)
}
/**
* track whether or not a message has timed out.
*/
trackTimeout() {
if (this.hasResponded) return
const soft = this.timeUntilTimeout()
const hard = this.timeUntilTimeout(true)
// Both values have to be not null otherwise we've timedout.
this.timedOut = !soft || !hard
if (!this.timedOut) {
clearTimeout(this.trackTimeoutId)
this.trackTimeoutId = setTimeout(
() => this.trackTimeout(),
Math.min(soft, hard)
).unref()
}
}
/**
* Safely parse the body into JSON.
*
* @return {Object}
*/
json() {
if (this.parsed == null) {
try {
this.parsed = JSON.parse(this.body)
} catch (err) {
throw new Error('Invalid JSON in Message')
}
}
return this.parsed
}
/**
* Returns in milliseconds the time until this message expires. Returns
* null if that time has already ellapsed. There are two different timeouts
* for a message. There are the soft timeouts that can be extended by touching
* the message. There is the hard timeout that cannot be exceeded without
* reconfiguring the nsqd.
*
* @param {Boolean} [hard=false]
* @return {Number|null}
*/
timeUntilTimeout(hard = false) {
if (this.hasResponded) return null
let delta
if (hard) {
delta = this.receivedOn + this.maxMsgTimeout - Date.now()
} else {
delta = this.lastTouched + this.msgTimeout - Date.now()
}
if (delta > 0) {
return delta
}
return null
}
/**
* Respond with a `FINISH` event.
*/
finish() {
this.respond(Message.FINISH, wire.finish(this.id))
}
/**
* Requeue the message with the specified amount of delay. If backoff is
* specifed, then the subscribed Readers will backoff.
*
* @param {Number} [delay=this.requeueDelay]
* @param {Boolean} [backoff=true] [description]
*/
requeue(delay = this.requeueDelay, backoff = true) {
this.respond(Message.REQUEUE, wire.requeue(this.id, delay))
if (backoff) {
this.emit(Message.BACKOFF)
}
}
/**
* Emit a `TOUCH` command. `TOUCH` command can be used to reset the timer
* on the nsqd side. This can be done repeatedly until the message
* is either FIN or REQ, up to the sending nsqd’s configured max_msg_timeout.
*/
touch() {
this.touchCount += 1
this.lastTouched = Date.now()
this.respond(Message.TOUCH, wire.touch(this.id))
}
/**
* Emit a `RESPOND` event.
*
* @param {Number} responseType
* @param {Buffer} wireData
* @return {undefined}
*/
respond(responseType, wireData) {
// TODO: Add a debug/warn when we moved to debug.js
if (this.hasResponded) return
process.nextTick(() => {
if (responseType !== Message.TOUCH) {
this.hasResponded = true
clearTimeout(this.trackTimeoutId)
this.trackTimeoutId = null
} else {
this.lastTouched = Date.now()
}
this.emit(Message.RESPOND, responseType, wireData)
})
}
}
module.exports = Message