Object property becomes undefined

Hi folks,

I am stuck with an issue in my Onvif Discovery node:

  1. An input message arrives.

  2. A reference to the input message is stored in node.triggerMsg:

    image

  3. Then a discovery is started (via a third-party library).

  4. The discovery triggers a "device" event when a device sends a response.

  5. My handleResult event handler function is called.

  6. In my handler function the node.triggerMsg is now undefined:

    image

I'm not sure why that node.triggerMsg becomes undefined. Is that because of the scope of the node variable? I have already changed var node = this; to let node = this;, but that doesn't help.

Thanks !!!
Bart

Try moving the function handleResult inside the node.on("input") and don't save a reference to msg (it should be in scope due to being an embedded function).

This should also avoid concurrency issues.

Alternatively, try cloning the msg as you assign it to the node variable before the call to probe.

Thanks Steve!

Indeed I also had been thinking about that, but it feeled a bit stupid having to register the same event handler over and over again. Only to make sure that Javascript uses the msg instance that is alive at that moment in time...

Ah a clone could also be a solution? I wasn't aware of that.

Did some further reading (e.g. this article) and I think I finally understand the Javascript scope, and why my code doesn't work. Please correct me if I am wrong...

image

  1. This { bracket is where the scope of the node variable starts.
  2. This } bracket is where the scope of the node variable ends.
  3. The callback handler function is called later on. But at that moment in time the scope of the variable has already ended, because the OnVifDiscoveryNode function has already been executed (by the event loop) completely. So as soon as the event handler function is called, it will work with the LAST known value of the node variable. Which means the node variable as it was at the end of its scope, i.e. at the } bracket (from point 2). And at that location, the node.triggerMsg was still undefined ...

Hi Bart, did you see my recent thread on re-structuring the .js file? I think the alternative way gives much better separation and understanding of the different scopes and illustrates ways to retain scope references.


Here is my example test node:

/** A simple template for defining custom nodes
 * Destructured to make for easier and more consistent logic.
 */
'use strict'

//#region ----- Module level variables ---- //

/** Main variables - acts as a configuration object
 *  that can easily be passed around.
 */
const mod = {
    /** @type {runtimeRED} Reference to the master RED instance */
    RED: undefined,
    /** @type {string} Custom Node Name - has to match with html file and package.json `red` section */
    nodeName: 'jktest2',

    // Add anything else here that you may wish
    // to access from any function.
    // Having one object also makes it much easier
    // to pass this to external modules as needed.
}

//#endregion ----- Module level variables ---- //

//#region ----- Module-level support functions ----- //

/** 1a) Runs once when Node-RED (re)starts or when the node package is first added */
function moduleSetup() {
    // As a module-level named function, it will inherit `mod` and other module-level variables
    //const RED = mod.RED

    console.log('>>>=[1a]=>>> [moduleSetup] Startng')

    // Do stuff here that only needs doing once
    // Don't forget to push anything that might be needed by other functions and modules
    // into the `mod` variable so that it is easily accessible and can be passed on.
}

/** 3) Run whenever a node instance receives a new input msg
 * NOTE: `this` context is still the parent (nodeInstance).
 * See https://nodered.org/blog/2019/09/20/node-done 
 * @param {object} msg The msg object received.
 * @param {Function} send Per msg send function, node-red v1+
 * @param {Function} done Per msg finish function, node-red v1+
 */
function inputMsgHandler(msg, send, done) {
    // As a module-level named function, it will inherit `mod` and other module-level variables
    //const RED = mod.RED

    console.log('>>>=[3]=>>> [inputMsgHandler] Startng', msg) //, this)
    send(msg)
    done()
}

/** 2) This is run when an actual instance of our node is committed to a flow
 * @param {runtimeNodeConfig} config The Node-RED node instance config object
 */
function nodeInstance(config) {
    // As a module-level named function, it will inherit `mod` and other module-level variables
    const RED = mod.RED

    console.log('>>>=[2]=>>> [nodeInstance] Startng')
    //console.log('>>>=[2a]=>>>', config)

    // Create the node instance - `this` can only be referenced AFTER here
    RED.nodes.createNode(this, config) 

    // Transfer config items from the Editor panel to the runtime
    this.name = config.name
    this.topic = config.topic

    /** Handle incoming msg's - note that the handler fn inherits `this`
     *  The inputMsgHandler function is executed every time this instance
     *  of the node receives a msg in a flow.
     */
    this.on('input', inputMsgHandler)


    /** Put things here if you need to do anything when a node instance is removed
     * Or if Node-RED is shutting down.
     * Note the use of an arrow function, this ensures that the function keeps the
     * same `this` context and so has access to all of the nodes properties.
     */
    this.on('close', (removed, done) => { 
        console.log('>>>=[4]=>>> [nodeInstance:close] Closing. Removed?: ', removed)
        

        // Give Node-RED a clue when you have finished (more important if your shutdown
        // process includes an async task, make sure done() is executed when the async
        // task completes, not when this function ends).
        done()
    })

    /** Properties of `this`
     * Methods: updateWires(wires), context(), on(event,callback), emit(event,...args), removeListener(name,listener), removeAllListeners(name), close(removed)
     *          send(msg), receive(msg), log(msg), warn(msg), error(logMessage,msg), debug(msg), trace(msg), metric(eventname, msg, metricValue), status(status)
     * Other: credentials, id, type, z, wires, x, y
     * + any props added manually from config, typically at least name and topic
     */
    //console.log('>>>=[2b]=>>>', this)
}

//#endregion ----- Module-level support functions ----- //

/** 1) Complete module definition for our Node. This is where things actually start.
 * @param {runtimeRED} RED The Node-RED runtime object
 */
function Test2(RED) {
    // As a module-level named function, it will inherit `mod` and other module-level variables

    // Save a reference to the RED runtime for convenience
    // This allows you to access it from any other function
    // defined above.
    mod.RED = RED

    // Add function calls here for setup that only needs doing
    // once. Node-RED loads this once no matter how many instances
    // of you node you add to flows.
    moduleSetup() // (1a)

    // Register a new instance of the specified node type
    RED.nodes.registerType(mod.nodeName, nodeInstance)
}

// Export the module definition, this is consumed by Node-RED on startup.
module.exports = Test2

//EOF

Hi Julian,
That is a nice well documented overview. Thanks for sharing!
It indeeds gives a good summary of the different scopes.

1 Like

Thanks. That node is also included in node-red-contrib-jktesting (as the jktest2 node) which I've just updated (though I'm afraid the docs didn't get updated, sorry. I was too busy playing with getting GitHub tagging working via Gulp.

If you do install that package, just note that you tend to get quite a lot of debug output in your logs :slight_smile:

1 Like