Uibuilder getting error "RangeError: Maximum call stack size exceeded"

I have a Node-RED API, once it send msg.payload to the mongoDB, I also send a flow ( function below ) back to uibuilder so other browser client will be updated.

Function sending into uibuilder to FORCE other browser to set score = 0;

global.set("score"), 0 )
msg.payload = {
   score: 0
}
return msg;

That error is often caused by a loop of some sort. Is it possible that sending that message triggers events that end up with coming back to the same code again and sending it again? I presume you have checked that if you remove the wire on the output of that function node that it does not happen? What happens if you go from that node to an RBE and then on to the rest of the flow?

Hi Stanley, thanks for switching from Slack.

If you wrap your code with back-ticks, it is easier to read.

As Colin says, we need to start by finding out where in the flow, the overflow is happening. So I would recommend starting by disconnecting the uibuilder input.

The error message in the debug sidebar should also tell you which node is causing the issue. Could you also post a screenshot of the relevant part of the flow?

Here is the screen shot..

The trigger came from - <delay 2s> - score:0

highscore is uibuilder is the error

Notice that you have a potential loop through Send score the browser -> highscore -> Send score to browser -> .....
Did you try my suggestion of putting an RBE on the output of Send score. Or on the input if you prefer.

1 Like

Yes, what is in that function node?

I don't think it is necessary since the only input to it comes from the front-end code anyway so I can't see what purpose it serves. You are already sending the score from various sources direct to uibuilder so the front-end code already has it.

The "Send score to browser" node, this is so that the global "score" value get updated to the browser when new browser comes in ...

Without this "loop", there were no "trigger" for updated score when new browser is connected....

I m only updating the score mqtt ( rexkl ) when the game is over and gets reset to 0 after 90 seconds... ( users have 90secs to fill in the forms )

msg.payload = {
    score : global.get("score"),
};

return msg;

The "delay 2s" node linked to score:0 was the node that causes the above error “RangeError: Maximum call stack size exceeded”

Once removed, the error goes away....

Ah, well you need to look at the caching examples I've added to the WIKI and the library. This will indicate a better way to do that which may help.

Thinking of it as a cache means that you will realise that the cache function needs to be the node that you send the data to rather than directly to uibuilder. Then you connect the 2nd output of uibuilder to the cache function since that contains the control messages that tell you when a new client connects or reconnects.

That way, you avoid a loop.

Thanks, I follow your wiki but the npm below is not found ...

npm install node-red-contrib-infocache

Thank for the links ....

I tried to tick or increase the buttons but when I loaded a new browser, the click counter is still zero ...

To make it work, all the "interested" messages must be set to homeMsgs contexts first right before it enters into the uibuilder node ?

Can you give a full example flows ?

No changes necessary needed in the index.js ?

// Codes from your Wiki ...
// Expects input msgs with topic set

// saved context
var homeMsgs = context.get('homeMsgs') || {}

// Replay cache if requested
if ( msg.hasOwnProperty('cacheControl') && msg.cacheControl === 'REPLAY' ) {
    for (var topic in homeMsgs) {
        // Only send to a single client if we can
        if ( msg.hasOwnProperty('_socketId') )
            node.send({
                "topic": topic, 
                "payload": homeMsgs[topic],
                "_socketId": msg._socketId
            })
        else
            node.send({
                "topic": topic, 
                "payload": homeMsgs[topic]
            })
    }
    return null
}

// ignore cacheControl and uibuilder control messages
if ( msg.hasOwnProperty('cacheControl') || msg.hasOwnProperty('uibuilderCtrl') ) return null

// Keep the last msg.payload by topic
homeMsgs[msg.topic] = msg.payload

// save context for next time
context.set('homeMsgs', homeMsgs)

return msg;

Here is a full cache example where the caching is done by topic. This will be included in the next release of uibuilder in the library examples as it is easier to understand I think than the current example.

[{"id":"ae4ad803.98aa88","type":"uibuilder","z":"18cb249f.38bafb","name":"","topic":"","url":"cachetest1","fwdInMessages":false,"allowScripts":true,"allowStyles":true,"copyIndex":true,"showfolder":false,"x":530,"y":1300,"wires":[["47aeb5cb.97f31c"],["a330534f.2e678","4c2899fb.1cd318"]]},{"id":"47aeb5cb.97f31c","type":"debug","z":"18cb249f.38bafb","name":"data from ui","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":710,"y":1300,"wires":[]},{"id":"43f62f54.e076c","type":"function","z":"18cb249f.38bafb","name":"cache by topic","func":"/* jshint asi: true */\n\n// Expects input msgs with topic set\n\n// cache variable name - needs to be unique for each cache\nconst cacheVarName = 'msgCache'\n\n// get saved context\nlet msgCache = context.get(cacheVarName) || {}\n\n// Handle cache control messages\nif (msg.hasOwnProperty('cacheControl')) {\n    // Replay cache if requested\n    if (msg.cacheControl === 'REPLAY') {\n        for (let topic in msgCache) {\n            // Only send to a single client if we can\n            if ( msg.hasOwnProperty('_socketId') ) {\n                node.send({\n                    \"topic\": topic, \n                    \"payload\": msgCache[topic],\n                    \"_socketId\": msg._socketId\n                })\n            } else {\n                node.send({\n                    \"topic\": topic, \n                    \"payload\": msgCache[topic]\n                })\n            }\n        }\n        return null\n    } else if (msg.cacheControl === 'CLEAR') {\n        // Or clear the cach (no msg sent)\n        context.set(cacheVarName, {})\n        return null\n    } else {\n        // or do nothing\n        return null\n    }\n}\n\n\n// ignore other uibuilder control messages\nif ( msg.hasOwnProperty('uibuilderCtrl') ) return null\n\n// Keep the last msg.payload by topic\nmsgCache[msg.topic] = msg.payload\n\n// save context for next time\ncontext.set(cacheVarName, msgCache)\n\n// Show number of cached msgs in status\nnode.status({fill:'green',shape:'ring',text:'Cached: ' + Object.keys(msgCache).length})\n\nreturn msg;","outputs":1,"noerr":0,"x":330,"y":1300,"wires":[["ae4ad803.98aa88"]]},{"id":"a330534f.2e678","type":"debug","z":"18cb249f.38bafb","name":"control msgs from ui","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":740,"y":1340,"wires":[]},{"id":"122cefe6.c39a7","type":"link in","z":"18cb249f.38bafb","name":"home-replay","links":["4c2899fb.1cd318","8c23b9bc.b992b8"],"x":215,"y":1300,"wires":[["43f62f54.e076c"]]},{"id":"4c2899fb.1cd318","type":"link out","z":"18cb249f.38bafb","name":"home-controls","links":["122cefe6.c39a7"],"x":655,"y":1380,"wires":[]},{"id":"a22357f8.abfa88","type":"comment","z":"18cb249f.38bafb","name":"Caching using simple function by msg.topic","info":"See https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Cache-and-Replay-Messages-without-using-node-red-contrib-infocache\nfor details.\n\nThe uibuilder control messages are used to \nreplay or empty the cache.\n\nFeed in your own data to the cache to see how it\nworks.\n\nThe way it is currently written, the last message\nof each topic passing through the function node\nis retained and then replayed as separate messages\nwhen a new client GETs the uibuilder URL (or when\nand existing client reloads the page).\n\n## Configuration\n\nReplace default `index.html` and `index.js` files\nwith those in the 2 comment nodes.\n\nChange the name of the cache variable if copying this function.","x":700,"y":1260,"wires":[]},{"id":"7ec2ab22.136164","type":"inject","z":"18cb249f.38bafb","name":"Input Trigger","topic":"RANDOM-TEST","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":155,"y":1260,"wires":[["9d09f684.33be68"]],"outputLabels":["Trigger"],"l":false},{"id":"9d09f684.33be68","type":"change","z":"18cb249f.38bafb","name":"Random #","rules":[{"t":"set","p":"payload","pt":"msg","to":"$random()*10","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":215,"y":1260,"wires":[["43f62f54.e076c"]],"inputLabels":["Trigger"],"outputLabels":["Random Number"],"l":false},{"id":"6dc4ac8d.557764","type":"comment","z":"18cb249f.38bafb","name":"index.html","info":"<!doctype html>\n<html lang=\"en\">\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n    <meta name=\"viewport\" content=\"width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes\">\n\n    <title>Cache Test : uibuilder</title>\n    <meta name=\"description\" content=\"Node-RED UI Builder Cache Test\">\n\n    <link rel=\"icon\" href=\"./images/node-blue.ico\">\n\n    <link type=\"text/css\" rel=\"stylesheet\" href=\"../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css\" />\n    <link type=\"text/css\" rel=\"stylesheet\" href=\"../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css\" />\n\n    <link rel=\"stylesheet\" href=\"./index.css\" media=\"all\">\n</head><body>\n    <div id=\"app\">\n        <b-container id=\"app_container\">\n            <h1>\n                UIbuilder Cache Test Example\n            </h1>\n            <p>\n                This is a uibuilder test using a simple function as a cache.\n                Trigger the input a couple of times then load a new window or tab.\n                The value below should almost instantly go from '[Nothing]' to\n                that last sent value. However, the number of received messages should\n                drop back down to 2.\n            </p>\n            <h2>Data from Node-RED</h2>\n            <b-card>\n                Random Number: {{nrRandom}}\n            </b-card>\n\n            <p>\n                Messages: Received={{msgsReceived}}\n            </p>\n\n        </b-container>\n    </div>\n\n    <!-- These MUST be in the right order. Note no leading / -->\n    <!-- REQUIRED: Socket.IO is loaded only once for all instances\n                     Without this, you don't get a websocket connection -->\n    <script src=\"../uibuilder/vendor/socket.io/socket.io.js\"></script>\n\n    <!-- --- Vendor Libraries - Load in the right order --- -->\n    <script src=\"../uibuilder/vendor/vue/dist/vue.js\"></script> <!-- dev version with component compiler -->\n    <!-- <script src=\"../uibuilder/vendor/vue/dist/vue.min.js\"></script>   prod version with component compiler -->\n    <!-- <script src=\"../uibuilder/vendor/vue/dist/vue.runtime.min.js\"></script>   prod version without component compiler -->\n    <!-- <script src=\"https://unpkg.com/babel-polyfill@latest/dist/polyfill.min.js\"></script> for older browsers -->\n    <script src=\"../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js\"></script>\n\n    <!-- REQUIRED: Sets up Socket listeners and the msg object -->\n    <!-- <script src=\"./uibuilderfe.js\"></script>   //dev version -->\n    <script src=\"./uibuilderfe.min.js\"></script> <!--    //prod version -->\n    <!-- OPTIONAL: You probably want this. Put your custom code here -->\n    <script src=\"./index.js\"></script>\n\n</body></html>\n","x":320,"y":1260,"wires":[],"icon":"node-red/parser-html.svg"},{"id":"243438af.12e268","type":"comment","z":"18cb249f.38bafb","name":"index.js","info":"/* jshint browser: true, esversion: 5, asi: true */\n/* globals document,Vue,window,uibuilder */\n// @ts-nocheck\n/*\n  Copyright (c) 2019 Julian Knight (Totally Information)\n\n  Licensed under the Apache License, Version 2.0 (the \"License\");\n  you may not use this file except in compliance with the License.\n  You may obtain a copy of the License at\n\n  http://www.apache.org/licenses/LICENSE-2.0\n\n  Unless required by applicable law or agreed to in writing, software\n  distributed under the License is distributed on an \"AS IS\" BASIS,\n  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  See the License for the specific language governing permissions and\n  limitations under the License.\n*/\n\n/** @see https://github.com/TotallyInformation/node-red-contrib-uibuilder/wiki/Front-End-Library---available-properties-and-methods */\n\nconst app1 = new Vue({\n    el: '#app',\n    data: {\n        nrRandom    : '[Nothing]',\n        msgsReceived: 0,\n        msgsControl : 0,\n        msgsSent    : 0,\n        msgRecvd    : '[Nothing]',\n        msgSent     : '[Nothing]',\n        msgCtrl     : '[Nothing]',\n    }, // --- End of data --- //\n    computed: {\n    }, // --- End of computed --- //\n    methods: {\n    }, // --- End of methods --- //\n\n    // Available hooks: init,mounted,updated,destroyed\n    mounted: function(){\n\n        /** **REQUIRED** Start uibuilder comms with Node-RED @since v2.0.0-dev3\n         * Pass the namespace and ioPath variables if hosting page is not in the instance root folder\n         * e.g. If you get continual `uibuilderfe:ioSetup: SOCKET CONNECT ERROR` error messages.\n         * e.g. uibuilder.start('/nr/uib', '/nr/uibuilder/vendor/socket.io') // change to use your paths/names\n         */\n        uibuilder.start()\n\n        var vueApp = this\n\n        // If msg changes - msg is updated when a standard msg is received from Node-RED over Socket.IO\n        // Note that you can also listen for 'msgsReceived' as they are updated at the same time\n        // but newVal relates to the attribute being listened to.\n        uibuilder.onChange('msg', function(msg){\n            console.info('[uibuilder.onChange] property msg changed!', msg)\n            vueApp.msgRecvd = msg\n            vueApp.msgsReceived++\n\n            vueApp.nrRandom = msg.payload\n\n        })\n\n    } // --- End of mounted hook --- //\n\n}) // --- End of app1 --- //\n\n// EOF\n","x":450,"y":1260,"wires":[],"icon":"font-awesome/fa-code"},{"id":"75c87fd8.f149a","type":"inject","z":"18cb249f.38bafb","name":"","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":215,"y":1380,"wires":[["27c93453.13df8c"]],"l":false},{"id":"27c93453.13df8c","type":"change","z":"18cb249f.38bafb","name":"Clear Cache","rules":[{"t":"set","p":"cacheControl","pt":"msg","to":"CLEAR","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":1380,"wires":[["8c23b9bc.b992b8"]]},{"id":"8c23b9bc.b992b8","type":"link out","z":"18cb249f.38bafb","name":"","links":["122cefe6.c39a7"],"x":415,"y":1380,"wires":[]}]

Not quite. You can send anything into that cache function as long as you have set a msg.topic. homeMsgs is the name of the context variable that will be used. To see what is in the context variable, select the function node and switch your sidebar to view "Context Data" then press the refresh button on the "Node" section of that display.

This is what you will see after pressing the inject button on the above flow example:

Try passing other values in but with different topic strings and you will see that the msgCache object will get more entries.

The code you have shown above works the same way but the new version is slightly more complete and allows you to clear the cache using a msg with different data (also included in the example flow).

That is correct. The cache is purely on the node-red side. The uibuilderfe library automatically sends the control msgs required to make the cache work as you will see if you put a debug on the control output as in the example above. The example does require different html and js but that is just to get the example working, it is nothing to do with the cache.

1 Like