Error-circular - where is this coming from?

#21

About using context.myname @knolleary said

I thought that deprecated meant that at some point in the future it will be removed and therefore it should not be used.
A further question, am I right in thinking that if one asks for a local file store and a memory store, that when using the memory store the operation using context.set() and specifying the memory store is identical to using context.myname?

#22

@Andrei
Right! the example is in the Wiki (Fading)

@hazymat
The Fadingexample above can do more than you need, so I didn't mention it.
But it can be simplified for your use case.

#23

@Colin
I'm surprised that you don't use the contextStorage property.
This is one of the features I was waiting since I'm using Node-RED. I guess 80% or more running v0.19 or greater use it with the default localfilesystem.

#24

@coliin

is identical to using context.myname ?

no - that is why it is "deprecated". As this thread has shown using .set works by serialising the data. This is necessary if you want to store the data "offboard" eg in a file / database / over the network. Whereas context.myname can handle non-serialisable data like objects containing functions or circular references - and is ONLY held in memory.
So in most cases it will appear the same to the user... but is NOT identical.

#25

Has that always been the case? If so then the circular error seen is not inherent in storing timer id using context.set but must be something about this specific use case.
Back on the deprecation of context.myname. If it has a useful purpose that is not covered by context.set then I would have thought it cannot be deprecated. Unless my understanding of deprecated is wrong (that it should not be used as it will be removed from node-RED at some point).

#26

I use MQTT with retained topics for this. It has a number of advantages:

  1. Any subscribers to the topic get notified when the value changes and can take action. This does not happen with persistent context variables.
  2. The persistent values are available across systems. For example I have similar dashboards in a number of different node-red systems. They can all subscribe to the same topics on the same server (where appropriate) so that, for example, if I have a switch on the dashboard and I switch it on one system then that is immediately echoed on the dashboards on the other systems.
  3. Automatic initialisation of dependent objects on re-deploy. If I redeploy node-RED then the switch mentioned above automatically gets initialised. To achieve that with context variables requires something like using inject nodes configured to inject the value at startup.

I am not saying there are no uses for persistent context, just that I have not found the need so far.

#27

BTW assuming you have defined contextStorage like:

    contextStorage: {
        default: {
	          module:"localfilesystem"
        },
        memoryOnly: {
            module: "memory"
        }
    },

You can do it this way if you want to avoid Error-circular:

context.set('id', id, 'memoryOnly');
and
context.get('id', 'memoryOnly');
#28

Yes. There is nothing in setTimeout that is circular - but the way he is calling his function from inside his function makes it so.

context is a javascript object - we can't remove it or stop people using it in normal javascript ways (such as adding a property) - but we can advise against using it if they want to be able to persist values when restarting Node-RED.

#29

Does that fix it for the users case? If so that appears to conflict with @dceejay's statement that context.set with memory store will still do the serialisation.

#30

I've tried several times and it seems to work.

#31

Deprecated means you shouldn't use it as there's no guarantee it will be there in the future. But nor is it a guarantee that it'll be removed.

In this instance, the only result of preventing a user from setting properties on the context object directly would be to break a lot of people's flows needlessly. There would be no benefit in doing that.

It was deprecated very early on in favour of context.set() because we did not want users to assume their flows would magically start persisting those values. That was long before we got into the detailed design and implementation work around persistent context.

During that work, it became clear there was still a role to play for setting context.foo directly - for volatile values that cannot be persisted, but have no need to be persisted. We just haven't spelt that out in the documentation.

#32

@dceejay, This seems to suggest that context.set of circular object with memory store is ok
image

[{"id":"e5ef82be.d668c8","type":"function","z":"514a90a5.c7bae8","name":"Circular context set","func":"let a = { b: null };\nlet b = { a: a };\na.b = b;\ncontext.set(\"a\", a);\nmsg.payload = context.get(\"a\");\nreturn msg;","outputs":1,"noerr":0,"x":321.5,"y":358,"wires":[["3e4a4081.cbe08"]]},{"id":"70f2ea5b.d5a6e4","type":"inject","z":"514a90a5.c7bae8","name":"Tryit","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":129.5,"y":359,"wires":[["e5ef82be.d668c8"]]},{"id":"3e4a4081.cbe08","type":"debug","z":"514a90a5.c7bae8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":534,"y":357,"wires":[]}]
#33

The in-memory store does not do any serialisation - it keeps the objects in memory, as-is. So you can throw any junk you want in there.

As soon as you switch the in-memory store for a file-based store, that junk will either get lost or cause errors.

#34

OK, that is what I initially assumed, but when I asked @dceejay that very question he appeared to give a different answer.

#35

IMHO as the context.myname is deprecated it'd be better to use:

context.set('id', id, 'memoryOnly');
#36

I'll try this later today when I get a chance to VPN home. Did you test it with my function, or with a different (test) function?

I don't fully understand everything in this thread, but it's fascinating nevertheless, and I can pick up various bits and pieces of things people say here and experiment myself...

#37

I modified you original function (and didn't change your "coding style"):

// Check for correct incoming values
if (!msg.reset &&
    (!msg.payload ||
     !msg.dimtime ||
     !msg.initialdelay)) {
    node.warn("Fader not correctly configured");
    return;
}

var thisnodeid = "fader_" + node.id;

// Get incoming values
var startval = Number(msg.payload); // just in case incoming was a string
var dimtime = msg.dimtime * 1000;
var initialdelay = msg.initialdelay * 1000;
var steptime = dimtime / startval;

// If we got a msg.reset, cancel timer, clear status,
//  clear function from memory, and don't restart
if (msg.reset) {
    var id = context.get(thisnodeid, 'memoryOnly');
    clearTimeout(id);
    node.status({});
    context.set(thisnodeid,"", 'memoryOnly');
    return;
}

// if context.get('id') has an object, then it's running
// and if it's running then clear the timer, clear the memory,
//  then continue (i.e. start over with new fader)
if (context.get('id') !== '') {
    var id = context.get(thisnodeid, 'memoryOnly');
    clearTimeout(id);
    node.status({});
    context.set(thisnodeid,"",'memoryOnly');
}

// Set initial light level
msg.payload = startval; // (use startval in case input was string)
node.send(msg);

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({});
            context.set(thisnodeid,"", 'memoryOnly');
            return;
        }
        id = setTimeout( f, steptime );
        context.set(thisnodeid,id, 'memoryOnly');
    }
    f();
},initialdelay)
context.set(thisnodeid,id, 'memoryOnly');

node.status({fill:"blue", shape:"ring", text:"Delay " + msg.initialdelay.toString() + "s"});

Note: you need this setting in the settings.js:

    contextStorage: {
        default: {
	          module:"localfilesystem"
        },
        memoryOnly: {
            module: "memory"
        }
    },
1 Like
#38

If you use context.thisnodeid then there's absolutely no need to modify settings.js to get this to work.

Please reread what I said about the deprecation of this feature. We are undeprecating it for this sort of use case.

1 Like
#39

We are going round in circles here, if context.set used with memory storage is the same as context.myvar then it can stay deprecated.

#40

No need to reread it, I agree! but I suspect new Node-RED's users will face this "issue" in the future.