Global.get in a function changes the global variable

This is strange. I got a list of my zigbee devices in an javascript array which are stored in global.set("devices"):

[
{"name":"Coordinator","status":"online","time":1701191747304,"color":"white","ignored":false,"alarm":false},
{"name": "device1".....}
]

I grab this table in a function in order to feed a HTML table to show status and time of last update in a easy to read form. Thus:

var dlist = global.get("devices")
for (const d of dlist) {
    node.warn(d);
    // loop over  objects in dlist
    var tstamp = new Date(d.time)
    var tstring = tstamp.toLocaleString('de-DE')
    d.time = tstring;
    d.color = "white"
    if (d.status == "offline") {
        d.color = "red"
    }
}
msg.payload = dlist
return msg

As you can see there is no global.set function called. Nevertheless when runnning the function the global variable devices is changed. The time stamp is now a string.

This is a very common question but quite difficult to search in the forum but the closest search term I think would be "by reference"

In short, when you store an object in JavaScript, you are NOT storing a copy, you are storing a pointer aka "by reference"

So when you modify the object you are modifying the same thing that is stored in context.

The solution is to ensure you store (or retrieve) an actual copy not a pointer.

For this, you can use RED.util.cloneMessage

e.g.

const dlist = RED.util.cloneMessage(global.get("devices"))
1 Like

I already thought about a "by reference" issue. Actuall this is the first time I want a function to get a global variable and modify it.
I tried the RED.util.cloneMessage function but it throws an array in the loop:

for (const d of dlist) 
var dlist: object

Type 'object' must have a '[Symbol.iterator]()' method that returns an iterator.(2488)

as suddenly dlistis no longer an array but an object.

Can you show your full function code (since you updated it) and a screenshot of the object/array in the "Context viewer" (on the side bar)

Additionally, add

// DISABLE cloning const dlist = RED.util...
var dlist = global.get("devices") // revert temporarily
// ADD THIS 👇
node.warn({ dlist, "type": typeof dlist, proto: dlist.constructor?.name  })
// ADD THIS 👆
for (const d of dlist) {
    node.warn(d);
    // ...
    // ...
}

and show me what you see in the debug panel when the function runs

Ok that's interesting:
The global variable devices is an array of objects (filled by another function)
Getting the content into dlist results in an object with one array!
Node.warn output:

{"dlist":
[{"name":"Coordinator","status":"online","time":1701193123007,"color":"white","ignored":false,"alarm":false}],
"type":"object",
"proto":"Array"}

Extracting array elements in a loop gives each element as an object:

for (const d of dlist) {
    node.warn(d);.....

gives:

{"name":"Coordinator","status":"online","time":1701193123007,"color":"white","ignored":false,"alarm":false}

Now I am confused :wink:

Not really. You posted an array with one object! Thus the extract is correct:

{
    "dlist": [
        {
            "name": "Coordinator",
            "status": "online",
            "time": 1701193123007,
            "color": "white",
            "ignored": false,
            "alarm": false
        }
    ],
    "type": "object",
    "proto": "Array"
}

Please look at my first posting. That is the content of the global variable devices
And that is array of objects.

In practice it would not really matter as long as I can loop over this array to modify each object in dlist and return an output array without modifying the global variable

Actually this works now! The javascript editor still reports the error message (see above) but the function returns the desired array and does not modify the global variable!

Thanks!

let dlist = global.get("devices")
let result = [];    // <== create a new array for results
for (const d of dlist) {
    let dd = RED.util.cloneMessage(d);    // <== clone the current object
    node.warn(dd);
    // loop over  objects in dlist
    let tstamp = new Date(dd.time);    // <== begin to modify the newly created object
    let tstring = tstamp.toLocaleString('de-DE');
    dd.time = tstring;
    dd.color = "white";
    if (dd.status == "offline") {
        dd.color = "red";
    }
    result.push(dd);    // <== push the modified object to the results array
}
msg.payload = result;    // <== forward the results of your operation
return msg

Thanks! That code is straightforward and avoids the error message.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.