Object.assign() throws error!

Hi All

I'm creating my first node in a while and I am using Object.assign() to set default values on a config object. For some reason this throws an error:

18 Dec 08:05:37 - [error] TypeError: Cannot read property 'handleError' of undefined
    at VirtualDimmerConstructor.Node.error (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:524:30)
    at /usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:210:26
    at Object.trigger (/usr/lib/node_modules/node-red/node_modules/@node-red/util/lib/hooks.js:149:13)
    at VirtualDimmerConstructor.Node._emitInput (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:196:11)
    at VirtualDimmerConstructor.Node.emit (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:180:25)
    at VirtualDimmerConstructor.Node.receive (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:479:10)
    at Immediate.<anonymous> (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/flows/Flow.js:814:52)
    at processImmediate (internal/timers.js:464:21)

The code in question is:

const defaultConfig = {
    longPressDelay: 80,
    updateInterval: 100,
    maxBrightness: 100,
    minBrightness: 0,
};
const nodeInit = (RED) => {
    function VirtualDimmerConstructor(partialConfig) {
        const config = applyConfigDefaults(partialConfig);
        RED.nodes.createNode(this, config);
        this.on('input', (msg, send, done) => {
            send({ payload: 'hi' });
            if (done) {
                done();
            }
        });
    }
    RED.nodes.registerType('virtual-dimmer', VirtualDimmerConstructor);
};
function applyConfigDefaults(config) {
    return Object.assign(Object.assign({}, defaultConfig), config);
}
module.exports = nodeInit;

Object.assign() has been available since node 4 so I can't see why this would blow up...

I imagine that some people might come back and say "that's not how you're supposed to deal with defaults". This question is not about what the code is doing (currently this is very bare bones and just to make sure I can get a node working in node-red) but about why Object.Assign doesn't work. It's a feature that I use quite a bit in my coding so doing without it would be a pain!

Thanks

I can't see an issue with object assign.

Out of curiosity...

Can you try changing the line to Object.assign({}, defaultConfig, config)

Secondly, try setting config without the helper function eg const config = Object.assign({}, defaultConfig, partialConfig);

Lastly, again, to satisfy my curiosity, could you rearrange the code to use the more common pattern e.g...

​module​.​exports​ ​=​ ​function​ ​(​RED​)​ ​{
  function VirtualDimmerConstructor(partialConfig) {
        const config = ...
  }
 
 
 RED.nodes.registerType('virtual-dimmer', VirtualDimmerConstructor);
}

I updated the code to:

const defaultConfig = {
    longPressDelay: 80,
    updateInterval: 100,
    maxBrightness: 100,
    minBrightness: 0,
};

module.exports = (RED) => {
    function VirtualDimmerConstructor(partialConfig) {
        const config = Object.assign({}, defaultConfig, partialConfig);
        RED.nodes.createNode(this, config);
        this.on('input', (msg, send, done) => {
            send({ payload: 'hi ' + config.longPressDelay });
            if (done) {
                done();
            }
        });
    }
    RED.nodes.registerType('virtual-dimmer', VirtualDimmerConstructor);
};

and get the same issue:

18 Dec 11:43:15 - [error] TypeError: Cannot read property 'handleError' of undefined
    at VirtualDimmerConstructor.Node.error (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:524:30)
    at /usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:210:26
    at Object.trigger (/usr/lib/node_modules/node-red/node_modules/@node-red/util/lib/hooks.js:149:13)
    at VirtualDimmerConstructor.Node._emitInput (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:196:11)
    at VirtualDimmerConstructor.Node.emit (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:180:25)
    at VirtualDimmerConstructor.Node.receive (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/nodes/Node.js:479:10)
    at Immediate.<anonymous> (/usr/lib/node_modules/node-red/node_modules/@node-red/runtime/lib/flows/Flow.js:814:52)
    at processImmediate (internal/timers.js:464:21)

:slightly_frowning_face:

Couple of things. Firstly, what version of node.js are you running?

Then, you don't need to assigns I don't believe, Object.assign will merge multiple objects.

And lastly, you need to be careful that config doesn't itself contain an object property because Object.assign only does shallow not deep copies.

Object.assign() - JavaScript | MDN

Can you try again one more indulgence - without the arrow function.

Additionally, could you console.log the partialConfig object (before object.assign) and console.log config after object.assign

I think you misread my message :grinning:

AHH hahaha. Yep. I did :joy::joy::joy:

1 Like

I'm confused by @Steve-Mcl message. Object.assign can merge multiple objects... I must be missing something...

My intention here was to set default values for the node config. What I didn't realise was that I was effectively throwing away the entire node object and creating a new one that I was trying to assign property values to. Node red was unhappy about that (presubably as we were trying to give it a node object that it hadn't created and knew nothing about) and threw some error.

I presume that there is probably a map of node instances somewhere so my new object was not in it and stuff blew up.
Now that I know what was going on I'll delete that code and write it a different way!!! :smiley:
Thanks for the responses.

You don't have to start with an empty object {}, you can start with an existing object if you prefer. But you can have as many other arguments to the function as you like and they will all be merged into the first listed object. However, Object.assign only checks the top-level of objects and does not do any merging below that level - it just overwrites. The MDN entry has the details.

With the config object, what normally happens in your node's .js file is that you apply each config entry to the node's this object manually. So the easiest way to do what you want is to check whether the config entries with defaults have a real value in config and if not, apply the default value. There are lots of examples in existing nodes including my own uibuilder nodes. It is a very common requirement.

yup, thanks. I wasn't looking for deep merging, I was only wanting to default the top level config properties.

The reason that it started with an empty object was because the actual code I wrote (in typescript) was:

const config = {...defaultConfig, ...partialConfig};

which typescript then turns into 2 Object.assigns to a new empty object.

I'm pretty sure this is the cause of the error.

Node event handlers should not be defined as arrow functions as they don't get this defined properly.

Change into a proper function declaration and all should be well.

nope. It was the fact that was returning an incorrect node object to node red.
All arrow functions do is mainting the current this value. If this is undefined or bannana that same value will be used. An incorrect this value won't kill the function.
an arrow function is the same as: myFunction.apply(this, someParam)

I know full well what arrow functions do.

There was a similar issue a while ago where the fix was to not use arrow functions. I assumed you were hitting the same thing.

A previous version of the Node code set a custom this context for the event handlers to deal with scoping function calls to individual messages. But looking through the code (bits of which I haven't touched for a year or more) I can see I was wrong and it doesn't do that anymore.

What was wrong with it and how did you fix it? Given I can't see anything immediately wrong with the code you shared at the start, it would be helpful to know what you changed so we can spot it in the future.

The code that fixed it was along the lines of:

const nodeWithDefaults = Object.assign(node, defaultConfig);

rather than:

const nodeWithDefaults = Object.assign({}, node, defaultConfig);

In the first case we are retaining the same object instance for the node rather than returning a new node with a shallow copy of the old node's properties.

Have you checked that? I think that would always end up with the defaults?

yes, you're right. I was trying to demonstrate what fixed the exception and in the process got the defaulting wrong.

So. Something like this:

const config = Object.assign(defaultConfig, partialNode); // all config properties have a value

const node = Object.assign(partialNode, config); // assign all config properties to node