DEV Community

Lucas Weis Polesello
Lucas Weis Polesello

Posted on

NodeJS - Lazy Init Patterns

One of the main challenges when dealing w/ the async nature of NodeJS is initializing classes/clients that requires some sort of side effect - such as database connection, disk reads or whatsoever. Even the simple idea of waiting for the first use-case to connect/initialize a resource.

Besides Dependency Injection - I like to use two approaches for this:

1) Leaving it up to the client to call connect or any other synonym - easy as creating an async function as the example below

const redis = require('redis');
const crypto = require('crypto');
//PROS: Damn easy, simple and straight-forward

//CONS: This leaves the entire responsibility to the client
class DistributedDataStructure {
    constructor(){
        this.client = redis.createClient();
    }

    async connect(){
        return this.client.connect();
    }

    async add(staffName, reviewId){
        //Do some business here - idk,
        const accountName = await this.client.get(key);
        return this.client.sAdd(`v1:${accountName}:pending-reviews`, reviewId);
    }
}

(async () => {
    const ds = new DistributedDataStructure();
    await ds.connect();
    ds.add('Jerome', crypto.randomBytes(12).toString('hex'));
})()
Enter fullscreen mode Exit fullscreen mode

2) Proxying the access

In the real and wild-world we know that we have to deal w/ legacy code, legacy initialization methods and much more unexpected stuff - for this we have a second use-case which leverages the Proxy API for JS

Using Proxy it would look poorly-like

const redis = require('redis');
const { once } = require('events');
const crypto = require('crypto');

//PROS: No client responsibility - makes it easy for the client
//CONS: More complex and error prone
class ProxiedDistributedDataStructure {
    constructor(){
        this.client = redis.createClient();
        this.client.connect();
        return new Proxy(this, {
            get(target, property){
                const descriptor = target[property];
                if(!descriptor){
                    return;
                }
                if(target.isReady){
                    return descriptor;
                }
                return async function(){
                    await once(target.client, 'ready');
                    return descriptor.apply(target, arguments);
                }
            }
        });
    }

    async add(staffName, reviewId){
        //Do some business here - idk - like below
        const accountName = await this.client.get(staffName);
        return this.client.sAdd(`v1:${accountName}:pending-reviews`, reviewId);
    }
}

const client = new ProxiedDistributedDataStructure();
client.add('Jerome', crypto.randomBytes(12).toString('hex'));
Enter fullscreen mode Exit fullscreen mode

The main benefit for the second approach is that we can instantiate the objects in sync contexts and only treat the method calls as async - instead of needing to play around some dirty gimmicks to call connect and chain promises - even worse, callbackifying.

Dev.to Code Examples

Note: AFAIC from Redis V3^ we have an option legacyMode whenever creating the client which we can keep this lazy nature of Redis - doing client buffering of calls.

Top comments (0)