How to "interact" with another instance of a function?

(Although what I'm trying to achieve can be done with core nodes, the reason I'd like to do this is to further my understanding of Javascript and Node-RED in general.)

I have written this little sleep dimmer function (link to other forum post), but I'd like to implement some kind of msg.reset to cancel the sleep dimmer.

Is there a way I can interact with the original "thread" or instance, to stop it, when the function itself receives a value in msg.reset?

(If you can correct any of my assumptions or wrong ideas about this, I'd be grateful too)

If not, is there a better way to achieve this?

Assuming this msg.reset is being passed into the same node... then yes - you need to save the instance of the timeout context.foo = setTimeout(.... then it will be available so you can call clearTimeout(context.foo).

Of course this will mean you can only have one running at a time... so you may need to think how to handle that if another msg arrives to trigger another timeout... do you ignore it ? or cancel existing and restart ? or... up to you.

Thanks @dceejay. I'll go with one running at a time - I'd like to use the node status and I guess it would be impossible to show the status of multiple instances!

Will give that a go.

Well yes. If it’s controllong one set of lights would be tricky to have two rodents running. So you need to block that from occurring.

Just trying to get my head around this now.

Do you mean I should save a value into context to indicate that a given fadeout is running? Then look it up and clear the timeout? Or are you talking about storing the function itself in context? Can you give me a pointer as to how I might do that?

I've just read up on storing a function in a variable, but to be honest I don't fully understand.

the instance of the timeout. Exactly as I wrote it

I think I've not understood, because this is now happening:

fail

Here's the code I changed it to:

if (msg.reset == "yep") {
    clearTimeout(context.foo)
}
    
setTimeout(function(){
    var i = startval;
    function f() {
        if (i>0) {
            i--;
            msg.payload = i;
            node.send(msg);
            var fadetxt = "Fading " + (i*steptime/1000).toString() + "s, " + i.toString() + "/" + startval.toString();
            node.status({fill:"green", shape:"ring", text:fadetxt});
        } else {
            node.status({});
            return;
        }
        context.foo = setTimeout( f, steptime );
    }
    f();
},initialdelay)

You need to test whether msg.reset exists first, also consider whether you want to go on and restart the timer in the case of the reset message.

I thought we should use context.set() and context.get()

Something like this:

var id = setTimeout(...)
context.set('id', id);

and

var id = context.get('id');
clearTimeout(id);

well yes that is "more correct" and would allow you to persist the object... but in this case I don't think persistence would be required so I think it's simper to understand.

Alright!
One other benefit using context.set(): it is displayed in the menu Context Data.

I don't think I have a problem with the logic of testing for msg.reset, and knowing how to deal with different scenarios (e.g. discard, reset timer and start counting, etc.) but I am really struggling with how to clear the timeout.

I understand that I need to save the function into memory. I have chosen to save to global whilst I figure this out (just for my own testing and understanding, as this means I can use context explorer to see it, and also access it from another node to e.g. test clearing the timeout from another function).

  • My main function has a function within it, in order to be able to have 2 delays (initial delay, and repeating delay for each fading step).
  • I have set the outer function (the initial delay) to memory.
  • When I start the timer, I can use context explorer and I see "[Object Object]"

However when I load this back into memory from another function node, then clearTimeout, the fader just keeps going.

To make it clearer, here's my code (yes, I know I used "id", just ignore the name for now):

FADER FUNCTION

var id = setTimeout(function(){
    var i = startval;
    function f() {
        if (i>0) {
            i--;
            msg.payload = i;
            node.send(msg);
            var fadetxt = "Fading " + (i*steptime/1000).toString() + "s, " + i.toString() + "/" + startval.toString();
            node.status({fill:"green", shape:"ring", text:fadetxt});
        } else {
            node.status({});
            return;
        }
        setTimeout( f, steptime );
    }
    f();
},initialdelay)
global.set('id',id);

And then...

TIMER CLEAR FUNCTION (different node, which I can inject message to for testing)

var id = global.get('id');
clearTimeout(id);

When I inject a message into this second function, it carries on counting

You need to store the second setTimeout id:

var id = setTimeout(function(){
    var i = startval;
    function f() {
        if (i>0) {
            i--;
            msg.payload = i;
            node.send(msg);
            var fadetxt = "Fading " + (i*steptime/1000).toString() + "s, " + i.toString() + "/" + startval.toString();
            node.status({fill:"green", shape:"ring", text:fadetxt});
        } else {
            node.status({});
            return;
        }
        id = setTimeout( f, steptime );
        global.set('id',id);
    }
    f();
},initialdelay)
global.set('id',id);

BTW the setTimeout returns an ID:

The clearTimeout() method clears a timer set with the setTimeout() method.

The ID value returned by setTimeout() is used as the parameter for the clearTimeout() method.

See https://www.w3schools.com/jsref/met_win_cleartimeout.asp

1 Like

don't use global. It would mean you can only have one instance of your node (as they would all try to use the same global variable)... use a local context one instead.