@azteam/redis-async
Version:
N/A
256 lines (210 loc) • 7.15 kB
JavaScript
import Redis from 'ioredis';
import {timeout} from '@azteam/util';
class RedisAsync {
constructor(config) {
this.connected = false;
this.host = config.host;
this.port = config.port;
this.prefix = config.prefix;
this.subscriptions = {};
this.connect();
}
async waitConnection(n = 10) {
for (let i = 0; !this.connected || i < n; i += 1) {
await timeout(1000);
}
if (!this.connected) {
throw new Error('Redis not connected');
}
}
parsePrefix(name) {
if (this.prefix) {
return `${this.prefix}:${name}`;
}
return name;
}
connect() {
this._alert('connecting', 'Redis connecting...');
this.client = new Redis({
host: this.host,
port: this.port,
lazyConnect: true,
retryStrategy: () => {
this.connected = false;
this._alert('connect', 'Redis disconnected');
return null;
},
});
this.client.connect().catch((err) => {
this._alert('error', `Redis Connection Error: ${err}`);
});
this.client.on('connect', () => {
this.connected = true;
this._alert('connect', 'Redis connected');
});
this.client.on('close', () => {
this.connected = false;
this._alert('end', 'Redis end');
});
this.client.on('error', (err) => {
this._alert('error', `Redis Error${err}`);
});
}
async get(key, defaultValue = null) {
const prefixKey = this.parsePrefix(key);
if (this.connected) {
const data = await this.client.get(prefixKey);
if (data) {
return JSON.parse(data);
}
} else {
this.connect();
return null;
}
return defaultValue;
}
async ttl(key) {
const prefixKey = this.parsePrefix(key);
return this.client.ttl(prefixKey);
}
async expire(key, timeSecond = 86400) {
const prefixKey = this.parsePrefix(key);
return await this.client.expire(prefixKey, timeSecond);
}
async set(key, data, timeSecond = 86400, count = 0) {
const prefixKey = this.parsePrefix(key);
if (this.connected) {
await this.client.set(prefixKey, JSON.stringify(data));
if (timeSecond > 0) {
await this.expire(key, timeSecond);
}
return true;
}
this.connect();
if (count < 5) {
return this.set(key, data, timeSecond, count + 1);
}
return false;
}
async incr(key, timeSecond = 86400, count = 0) {
const prefixKey = this.parsePrefix(key);
if (this.connected) {
const result = await this.client.incr(prefixKey);
// Option B: Only set TTL if it's a new key (result is 1)
if (result === 1 && timeSecond > 0) {
await this.expire(key, timeSecond);
}
return result;
}
this.connect();
if (count < 5) {
return this.incr(key, timeSecond, count + 1);
}
return false;
}
// async scan(pattern, cursor = 0, count = 1000) {
// const scanAsync = promisify(this.client.scan).bind(this.client);
// return await scanAsync(cursor, 'MATCH', pattern, 'COUNT', count);
// }
async getKeys(pattern, count = 0) {
const regexKey = this.prefix ? `${this.prefix}:*${pattern}*` : `*${pattern}*`;
if (this.connected) {
const keys = await this.client.keys(regexKey);
if (this.prefix) {
const prefixPrefix = `${this.prefix}:`;
return keys.map((k) => k.replace(prefixPrefix, ''));
}
return keys;
}
this.connect();
if (count < 5) {
return this.getKeys(pattern, count + 1);
}
return [];
}
async remove(key, exact = true, count = 0) {
const prefixKey = this.parsePrefix(key);
if (this.connected) {
if (exact) {
await this.client.del(prefixKey);
} else {
const regexKey = `*${prefixKey}*`;
const keys = await this.client.keys(regexKey);
if (keys.length > 0) {
return this.client.del(...keys);
}
}
} else {
this.connect();
if (count < 5) {
return this.remove(key, exact, count + 1);
}
return false;
}
return false;
}
async publish(channel, data, count = 0) {
const prefixChannel = this.parsePrefix(channel);
if (this.connected) {
return this.client.publish(prefixChannel, JSON.stringify(data));
}
this.connect();
if (count < 5) {
return this.publish(channel, data, count + 1);
}
return false;
}
async subscribe(channel, callback) {
const prefixChannel = this.parsePrefix(channel);
if (!this.subscriberClient) {
this.subscriberClient = this.client.duplicate();
this.subscriberClient.connect().catch((err) => {
this._alert('error', `Redis Subscriber Error: ${err}`);
});
this.subscriberClient.on('message', (chan, message) => {
let parsedMsg = message;
try {
parsedMsg = JSON.parse(message);
} catch (e) {
// ignore if not JSON
}
if (this.subscriptions[chan]) {
this.subscriptions[chan].forEach((cb) => cb(parsedMsg));
}
});
}
if (!this.subscriptions[prefixChannel]) {
this.subscriptions[prefixChannel] = [];
await this.subscriberClient.subscribe(prefixChannel);
}
this.subscriptions[prefixChannel].push(callback);
}
async unsubscribe(channel, callback) {
const prefixChannel = this.parsePrefix(channel);
if (!this.subscriptions[prefixChannel]) {
return;
}
if (callback) {
this.subscriptions[prefixChannel] = this.subscriptions[prefixChannel].filter((cb) => cb !== callback);
} else {
this.subscriptions[prefixChannel] = [];
}
if (this.subscriptions[prefixChannel].length === 0) {
delete this.subscriptions[prefixChannel];
if (this.subscriberClient) {
await this.subscriberClient.unsubscribe(prefixChannel);
}
}
}
setAlertCallback(callback) {
this.alertCallback = callback;
}
_alert(status, msg) {
if (typeof this.alertCallback === 'function') {
this.alertCallback(status, msg);
} else {
console.error(status, msg);
}
}
}
export default RedisAsync;