skip to Main Content

My class

import * as redis from 'redis';

export class RedisClient {
    private client: any

    constructor() {
        this.client = redis.createClient()
    }

    public async set(key: string, value: any): Promise<void> {
        return new Promise((resolve, reject) => {
            this.client.set(key, value, (err: any) => {
                if (err) {
                    reject(err);
                } else {
                    resolve();
                }
            });
        });
    }

    public async get(key: string): Promise<string> {
        return new Promise((resolve, reject) => {
            this.client.get(key, (err: any, value: string | PromiseLike<string>) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(value);
                }
            });
        });
    }
}

I know I need to add this.client.connect() and I was hoping I could add it to constructor but that doesn’t work. I get client is closed. It does work if I add this.client.connect within the set method but is the approach considered good if we are connecting to Redis every single time before calling set/get?

Is there a better approach to this


Update:
Taking answer into suggestion, created with Singleton pattern. Added the following in RedisClient

let cached: Promise<RedisClient>;
static instance(): Promise<RedisClient> {
    if (!cached) cached = RedisClient.create();
    return cached;
}

In caller class

constructor() {
    this.redisClient = RedisClient.instance();
}
public async handle(event: DynamoDBStreamEvent): Promise<void> {
    const client = await this.redisClient;
    ....
}

This works but I am not too familiar with typescript. Is this considered a good pattern. My intention is that RedisClient is only instantiated once so we are not always re-connecting to Redis with every operation

2

Answers


  1. This is just a limitation of JavaScript and thus TypeScript. The .connect function is async and so you can’t put it in the constructor. What’s happening is that the resolution of the call to .connect is not happening before the rest of your code runs. You need to wait for .connect to resolve before you can use the Redis client.

    If I find myself writing this sort of class, I usually add a static method that calls .createClient and .connect together and then use that method instead of new. There’s a lot of ways to skin this cat, but here’s one:

    import { createClient } from 'redis'
    
    type RedisConnection = ReturnType<typeof createClient>
    
    export class RedisClient {
    
      private connection: RedisConnection
    
      constructor(connection: RedisClientConnection) {
        this.connection = connection
      }
    
      async static create() {
        const connection = redis.createClient()
        connection.on('error', (err) => console.log('Redis Client Error', err))
        await connection.connect()
    
        return new RedisClient(connection)
      }
    
      public async set(key: string, value: any): Promise<void> {
        return this.connection.set(key, value)
      }
    
      public async get(key: string): Promise<string> {
        return this.connection.get(key)
      }
    }
    

    Note a few things here:

    1. I renamed client to connection since you are creating a class called RedisClient as I thought it would be confusing to have two things called "Client".
    2. I added some error handling so you know what’s going on when stuff breaks. Optional, but recommended. And you might want to do something a bit more robust.
    3. I got rid of the pesky any for you using ReturnType! 😉
    4. I removed all the callback/promise logic. Node Redis 4.x supports promises now so there’s no need for it.

    You might even take this a step further and use a Singleton pattern here as Redis rarely needs more than one connection for a Node.js application.

    Login or Signup to reply.
  2. What i would do in your situation is the wrap the redis connection in a IIF and assign the result to the client property on the class

    import * as redis from 'redis';
    
    export class RedisClient {
    private client: any
    
    constructor() {
       (async()=>{
        this.client = redis.createClient();
        await this.client.connect()
       })()
    }
    
    public async set(key: string, value: any): Promise<void> {
        return new Promise((resolve, reject) => {
            this.client.set(key, value, (err: any) => {
                if (err) {
                    reject(err);
                } else {
                    resolve();
                }
            });
        });
    }
    
    public async get(key: string): Promise<string> {
        return new Promise((resolve, reject) => {
            this.client.get(key, (err: any, value: string | PromiseLike<string>) => {
                if (err) {
                    reject(err);
                } else {
                    resolve(value);
                }
            });
        });
    }
    

    }

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search