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