Skip to content

Instantly share code, notes, and snippets.

@giles-v
Created January 11, 2018 17:13
Show Gist options
  • Save giles-v/942598b8e4e19beab035270b2cc7e791 to your computer and use it in GitHub Desktop.
Save giles-v/942598b8e4e19beab035270b2cc7e791 to your computer and use it in GitHub Desktop.
Redis caching module which can reconnect if the connection drops
import redis from 'redis';
const RECONNECT_TIME_THRESHOLD = 10000;
const keyNotFoundError = new Error('Key not found');
keyNotFoundError.keyNotFound = true;
const internals = {
redis,
reconnectTimeout: null,
client: null,
error: new Error('Server not yet ready'),
info: msg => {
if (process.env.NODE_ENV === 'test') {
return;
}
console.log(msg);
}
};
/**
* Creates a new instance of the Redis client, and attaches listeners to
* it. On error events, it will queue up regeneration of the client (calling itself).
*
* @returns {Object} Redis client instance
*/
internals.createClient = () => {
const opts = {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT || '6379',
connect_timeout: 3000
};
const client = internals.redis.createClient(opts);
client.on('end', function () {
internals.info('Redis cache: connection ended.');
});
client.on('error', function (err) {
internals.client.quit();
if (internals.error && internals.reconnectTimeout) {
// we've already logged the issue, and are trying to reconnect.
return;
}
logger.error(err);
internals.error = err;
internals.reconnectTimeout = setTimeout(() => {
internals.info('Redis cache: attempting to reconnect...');
internals.client = internals.createClient();
internals.reconnectTimeout = null;
}, RECONNECT_TIME_THRESHOLD);
});
client.on('ready', function () {
internals.info('Redis cache: connection ready.');
internals.error = null;
});
return client;
};
/**
* Initialize (make initial connection)
*
* @returns {undefined} undefined
*/
export const init = () => {
if (internals.client) {
return;
}
internals.client = internals.createClient();
};
/**
* Set a value in the Redis cache.
*
* @param {string} key - data key
* @param {Object} value - data to store
* @param {number} ttl - expiry for the data. Pass 0 or null for no expiry.
* @param {Function} callback - function to call with an error or response
* @returns {undefined} undefined
*/
export const set = (key, value, ttl, callback) => {
const valueString = JSON.stringify(value);
internals.info(`Redis cache: setting key '${key}', data length ${valueString.length}`);
if (internals.error) {
return callback(internals.error);
}
if (ttl > 0) {
return internals.client.set(key, valueString, 'EX', ttl, callback);
}
internals.client.set(key, valueString, callback);
};
/**
* Get a value from the Redis cache.
*
* @param {string} key - data key
* @param {Function} callback - function to call with an error or response
* @returns {undefined} undefined
*/
export const get = (key, callback) => {
if (internals.error) {
internals.info(`Redis cache: attempting to get key '${key}'`);
return callback(internals.error);
}
internals.client.get(key, (err, reply) => {
if (err) {
return callback(err);
}
if (reply === null) {
internals.info(`Redis cache: key '${key}' not found`);
return callback(keyNotFoundError);
}
internals.info(`Redis cache: got key '${key}' with data length ${reply.length}`);
return callback(err, JSON.parse(reply));
});
};
/**
* Flush all keys from Redis.
*
* @param {Function} callback - function to call with an error or response
* @returns {undefined} undefined
*/
export const flushAll = callback => {
if (internals.error) {
return callback(internals.error);
}
internals.info('Redis cache: flushAll');
internals.client.flushall(callback);
};
/**
* Quit the Redis session.
*
* @returns {undefined} undefined
*/
export const quit = () => {
internals.info('Redis cache: quit');
internals.client.quit();
};
export const __internals__ = internals;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment