'Watch.js only unwatching in certain conditions?

I am developing an express-ws based app in Node.js, and I am using a Watch.js watcher within an app.ws route. When the WebSocket is opened, a watch is made on a global object, using a callback function defined within the app.ws statement. Then inside ws.on('close', ... function, I call unwatch with the same object and callback function. (See code snippet A)

This works for a simple JS object with simple boolean entries (See stat_A in snippet A). However, I will soon have multiple objects holding different statuses, and I would like to put them all in a new JS object. So that is an object with a bunch of objects inside.

Snippet B is an example that should be equivalent to Snippet A. However, it seems that after the WebSocket is closed, the unwatch somehow does not take, and when stat['A'] is updated, the program tries to send it over the closed WebSocket and crashes.

I am stumped. What is causing the unwatch to fail?

EDIT: I have gotten snippet B working by changing watch(stat, 'A', send_stat) to watch(stat['A'], send_stat), and also changing the unwatch in the same way. See snippet C. Now, I am unable to unwatch the main stat list, See snippet D.

Code snippet A (stat_A is updated elsewhere):

const express = require('express');
const app = express();
const expressWs = require('express-ws')(app);
const watchjs = require('watchjs');

const port = 1338;

var stat_A = {'1': true, '2': false, '3' : false};

app.ws(
    '/stat_A',
    (ws, req) => {
        function send_stat() {
            ws.send(JSON.stringify(stat_A));
        }

        send_stat();
        // Setup watchers
        watchjs.watch(stat_A, send_stat);

        ws.on(
            'close',
            () => {
                // Stop watchers
                watchjs.unwatch(stat_A, send_stat);
            }
        );
    }
);

app.listen(
    port,
    () => {
        console.log(`Listening (HTTP) on port ${port}!`);
    }
);

Code snippet B (stat['A'] is updated elsewhere):

const express = require('express');
const app = express();
const expressWs = require('express-ws')(app);
const watchjs = require('watchjs');

const port = 1338;

var stat = {
    'A': {'1': true, '2': false, '3' : false}
};

app.ws(
    '/stat_A',
    (ws, req) => {
        function send_stat() {
            ws.send(JSON.stringify(stat['A']));
        }

        send_stat();
        // Setup watchers
        watchjs.watch(stat, 'A', send_stat);

        ws.on(
            'close',
            () => {
                // Stop watchers
                watchjs.unwatch(stat, 'A', send_stat);
            }
        );
    }
);

app.listen(
    port,
    () => {
        console.log(`Listening (HTTP) on port ${port}!`);
    }
);

Code snippet C (B fixed):

const express = require('express');
const app = express();
const expressWs = require('express-ws')(app);
const watchjs = require('watchjs');

const port = 1338;

var stat = {
    'A': {'1': true, '2': false, '3' : false}
};

app.ws(
    '/stat_A',
    (ws, req) => {
        function send_stat() {
            ws.send(JSON.stringify(stat['A']));
        }

        send_stat();
        // Setup watchers
        watchjs.watch(stat['A'], send_stat);

        ws.on(
            'close',
            () => {
                // Stop watchers
                watchjs.unwatch(stat['A'], send_stat);
            }
        );
    }
);

app.listen(
    port,
    () => {
        console.log(`Listening (HTTP) on port ${port}!`);
    }
);

Code snippet D (Same issue as B, fix in C not applicable):

const express = require('express');
const app = express();
const expressWs = require('express-ws')(app);
const watchjs = require('watchjs');

const port = 1338;

var stat = {
    'A': {'1': true, '2': false, '3' : false}
};

app.ws(
    '/stat',
    (ws, req) => {
        function send_stat() {
            ws.send(JSON.stringify(stat));
        }

        send_stat();
        // Setup watchers
        watchjs.watch(stat, send_stat);

        ws.on(
            'close',
            () => {
                // Stop watchers
                watchjs.unwatch(stat, send_stat);
            }
        );
    }
);

app.listen(
    port,
    () => {
        console.log(`Listening (HTTP) on port ${port}!`);
    }
);


Solution 1:[1]

So the final answer to this is as below.

const express = require('express');
const app = express();
const expressWs = require('express-ws')(app);
const watchjs = require('watchjs');

const port = 1338;

var stat = {
    'A': {'1': true, '2': false, '3' : false}
};

app.ws(
    '/stat',
    (ws, req) => {
        function send_stat() {
            ws.send(JSON.stringify(stat));
        }

        send_stat();
        // Setup watchers
        Object.keys(stat).forEach((key) => {
            watchjs.watch(stat[key], send_stat);
        })

        ws.on(
            'close',
            () => {
                // Stop watchers
                Object.keys(stat).forEach((key) => {
                    watchjs.unwatch(stat[key], send_stat);
                })
            }
        );
    }
);

app.ws(
    '/stat/A',
    (ws, req) => {
        function send_stat() {
            ws.send(JSON.stringify(stat['A']));
        }

        send_stat();
        // Setup watcher
        watchjs.watch(stat['A'], send_stat);

        ws.on(
            'close',
            () => {
                // Stop watcher
                watchjs.unwatch(stat['A'], send_stat);
            }
        );
    }
);

app.listen(
    port,
    () => {
        console.log(`Listening (HTTP) on port ${port}!`);
    }
);

Solution 2:[2]

That can be made with 2 options, one with a setter/getter, one with a getter and a setInterval

// option with a setter/getter

function flagWatch(callback, cval = null)
{   
    this.callback = callback;
    this.get = () => cval;

    this.set = function(ncval) {
        if(cval != ncval)
        {
            let oldval = cval;
            cval = ncval;

            if(typeof this.callback == 'function')
            this.callback(ncval, oldval);
        }
    };

    return this;
};

var hr = new flagWatch((v, o) => {
    console.log('new value ['+v+'], old value ['+o+']');
});

setInterval(function() {
    hr.set(!hr.get());
}, 2000);
// option with a getter and a setInterval

function watchValue(getter, callback, interval = 1)
{
    if(typeof getter != 'function' || typeof callback != 'function')
    throw new Error('Getter or Callback is not a function');

    var cval = getter();

    setInterval(function() {
        var ncval = getter();

        if(cval != ncval)
        {
            let oldval = cval;
            cval = ncval;
            callback(ncval, oldval);
        }
    }, interval);
}

var t = true;

watchValue(() => t, (v, o) => {
    console.log('changed to ['+v+'] (was ['+o+'])');
});

setInterval(function() {
    t = !t;
}, 2000);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2