NodeJS - Lazy Init Patterns
Published: 2023-12-24 21:22:51

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
```javascript
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'));
})()
```


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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy)

Using Proxy it would look poorly-like

```javascript
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'));
```

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
- [First Approach](https://github.com/lukas8219/devto-examples/blob/master/lazy-init/lazy-init-1.js)
- [Second Approach](https://github.com/lukas8219/devto-examples/blob/master/lazy-init/lazy-init-2.js)

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.