0

How do I persist changes to values inside a Singleton class object?

I set and get a class object value with a setter and getter method like:

wwjsInstances.setInstance("name1", client);
wwjsInstances.getInstance("name1")

My wwjsInstances.js:


/*
|--------------------------------------------------------------------------
| Singleton : wwjsInstances Class
|--------------------------------------------------------------------------
*/
class wwjsInstances {

    // Var > Initialize
    message;
    instances = {};

    /*
    |--------------------------------------------------------------------------
    | Constructor
    |--------------------------------------------------------------------------
    */
    constructor() {
        this.message = 'I am an instance';
    }

    /*
    |--------------------------------------------------------------------------
    | Method
    |--------------------------------------------------------------------------
    */
    setInstance(clientId, clientSession) {

        try {

            this.instances[clientId] = clientSession;

        } catch (err) {

            throw new Error("[wwjsInstances] : Failed to Attach Session to Singleton", err);

        }

        return true;
    }

    /*
    |--------------------------------------------------------------------------
    | Method
    |--------------------------------------------------------------------------
    */
    getInstance( clientId ){

        return this.instances[clientId]

    }

}

module.exports = new wwjsInstances();

The value of the class object is another object ( non UI WhatsApp Web Client Object, used for all WhatsApp operations )

Inside my /server.js file, I got this snippet:


/*
|--------------------------------------------------------------------------
| Whatsapp Client
|--------------------------------------------------------------------------
*/

const wwjsServiceModule = require("./app/services/wwjsService");
const wwjsInstances = require('./app/services/wwjsInstances');

// Starts > Non-UI WhatsApp Web App
wwjsServiceModule.createSession("name1").then((client) => {

    // Add > Instance Object [ Non-UI WhatsApp Web App ] > To > Singleton > Object Array [ There can be multiple clients running concurrently ]
    wwjsInstances.setInstance("name1", client);
    
    // Log
    console.log('wwjsInstances.getInstance: ', wwjsInstances.getInstance("name1")); // works, sample log below:
    
    // <ref *1> Client
    // {
    //     _events: [Object
    // :
    //     null
    //     prototype
    // ]
    //     {
    //         authenticated: [Function(anonymous)],
    //             auth_failure
    //     :
    //         [Function(anonymous)],
    //             loading_screen
    //     :
    //         [Function(anonymous)],
    //             qr
    //     :
    //         [Function(anonymous)],
    //             message
    //     :
    //         [AsyncFunction(anonymous)],
    //             message_create
    //     :
    //         [Function(anonymous)],
    //      
    //      ....
    //            
    //
    //     }
    //
    // }

});

The wwjsInstances.getInstance("name1") in the .then() callback works and it returns the Client object

I got another .js file named process.js, it is a .js file I run with node ./app/queues/process.js to run & process my jobs. I am using node.js OptimalBits/bull queue

This is my process.js file:


const EventEmitter = require("events");
EventEmitter.defaultMaxListeners = 50;

// The processor to define/link [ Job Processing Function ]
const processorModule = require("../tasks/File/processor");

// The producer [ Queue ]
const { uploadFileByIdQueue, sendWhatsAppMessageQueue  } = require(".");

const wwjsServiceModule = require("../services/wwjsService");
const wwjsInstances = require('../../app/services/wwjsInstances');


/*
|--------------------------------------------------------------------------
| Handler : Failure
|--------------------------------------------------------------------------
*/
const handleFailure = (job, err) => {
    if (job.attemptsMade >= job.opts.attempts) {

        console.log(`Job failures above threshold in ${job.queue.name} for: ${JSON.stringify(
            job.data
        )}`);

        job.remove();
        return null;
    }

    console.log(`Job in ${job.queue.name} failed for: ${JSON.stringify(job.data)} with ${
        err.message
    }. ${job.opts.attempts - job.attemptsMade} attempts left`);

};


/*
|--------------------------------------------------------------------------
| Handler : Completed
|--------------------------------------------------------------------------
*/
const handleCompleted = job => {

    console.log(`Job in ${job.queue.name} completed for: ${JSON.stringify(job.data)}`);

    job.remove();

};


/*
|--------------------------------------------------------------------------
| Handler : Stalled
|--------------------------------------------------------------------------
*/
const handleStalled = job => {

    console.log(`Job in ${job.queue.name} stalled for: ${JSON.stringify(job.data)}`);

};



/*
|--------------------------------------------------------------------------
| Queue(s) > Active
|--------------------------------------------------------------------------
*/
const activeQueues = [
    // {
    //     queue: uploadFileByIdQueue, // Queue
    //     processor: processorModule.uploadFileByIdProcessor // Processor
    // },
    {
        queue: sendWhatsAppMessageQueue, // Queue
        processor: processorModule.sendWhatsAppProcessor // Processor
    }
];



/*
|--------------------------------------------------------------------------
| Queue(s) > For Each > Loop
|--------------------------------------------------------------------------
*/
activeQueues.forEach(handler => {

    console.log('[WhatsApp] : process.js > activeQueues.forEach() > wwjsInstances > Singleton: ', wwjsInstances);
    // [WRONG, NOT UPDATED] > Result: wwjsInstances { message: 'I am an instance', instances: {} }

    let client = wwjsInstances.getInstance("foodcrush")

    console.log('[WhatsApp] : process.js > activeQueues.forEach() > wwjsInstances.getInstance("foodcrush"):', client);
    // [WRONG, NOT UPDATED] > Result: undefined


    // Get > Queue
    const queue = handler.queue;

    // Get > Processor
    const processor = handler.processor;

    const failHandler = handler.failHandler || handleFailure;
    const completedHandler = handler.completedHandler || handleCompleted;

    // here are samples of listener events : "failed","completed","stalled", the other events will be ignored
    queue.on("failed", failHandler);
    queue.on("completed", completedHandler);
    queue.on("stalled", handleStalled);

    queue.process(processor);// link the correspondant processor/worker

    console.log(`Queue > getJobs: ${JSON.stringify(queue.getJobs(), null, 2)}...`);
    console.log(`Processing ${queue.name}...`);

});

As you can see with the process.js I ran with node ./app/queues/process.js, the Singleton class object value (instance) is never updated,

it still shows the old default value defined in the .js class file

wwjsInstances returns { message: 'I am an instance', instances: {} } and wwjsInstances.getInstance("foodcrush") returns undefined

the update to the instance class object value done in /server.js never persisted, why is that?

yeln
  • 462
  • 2
  • 10
  • 23
  • Are you sure `setInstance()` is being called before you're logging `wwjsInstances`? The `wwjsInstances` class doesn't look like it has a `setInstance` method, maybe your code is crashing because you're calling a method that doesn't exist? `_instances = [];` is probably better declared as an object `_instances = {};` if you're going to be adding non numeric keys to it. NodeJS should be able to log those non-numeric keys of the array, so I don't think that would be your issue though. – Nick Parsons Nov 24 '22 at 09:45
  • hi, sorry its just a typo, I have updated the `singleton` class, its still the same, `wwjsInstances { message: 'I am an instance', instances: {} }` any changes in `index.js` was never transfered to `text.js` even with the `require(...)`, is the changes even supposed to persist? – yeln Nov 24 '22 at 09:58
  • Yes the changes should persist. If you add a log to `setInstance()` do you see that appear log before your log in the test.js file? – Nick Parsons Nov 24 '22 at 10:03
  • I set the `instances` inside `index.js` file, and imported it into `text.js` both file have no connection/relationship at all – yeln Nov 24 '22 at 10:05
  • Yeah, even though both files may not be related they're both using the same singleton instance, which is represented by the same reference throughout your entire app, so modifying it in `index.js` should mean that change is visible in `test.js`. The only thing that I can think of is that `.setInstance()` is being called after and not before you're trying to log it with `console.log('wwjsInstances: ', wwjsInstances);` inside of `test.js` (that's why I was wondering about what occurs if you add a log to `setInstance` so that you can check what order things are executing) – Nick Parsons Nov 24 '22 at 10:17
  • `test.js` is actually a `queue` (`optimalbits/bull`), I run it like `node ./app/queues/process/test.js`, I checked the `singleton` in a `nodejs` `controller.js` file and it shows the updated property values. but in the `queue` file I started with `node ./app/queues/process/test.js`, it is showing the default values, why is the singleton state not in sync – yeln Nov 24 '22 at 10:34
  • 1
    You know how to [edit]. I recommend to use that to add more info. – Yunnosch Nov 24 '22 at 10:36
  • I guess one issue is that your single instance gets recreated every time you import it. You should update the constructor to flag once that instance has been created and then reuse that instance: [Simplest/cleanest way to implement a singleton in JavaScript](https://stackoverflow.com/a/59646297) – Nick Parsons Nov 25 '22 at 09:16
  • Also, I assume that when you run `node ./app/queues/process.js` something also starts the sever.js file. Otherwise, you have two separate independent programs (one for server.js running and another for process.js) which both have their own memory, so they would each have their own singleton instance. – Nick Parsons Nov 25 '22 at 09:19

0 Answers0