Can I have a delay / sleep inside Function node

Is it a value you set once ? Or is it dynamic ?

Ideally dynamic, as the system could adjust its own timing to compensate for additional tasks it receives from outside sources. Thatā€™s why Iā€™d like to use a function.

1 Like

Here's a description of the current project, for some context, if you're interested :slight_smile:

Let's me go back to the original question. I have a logic where I need to implement some delay. I have to do it in a way that I stay inside a loop of my existing logic.

I did this proof of concept code which should delay the output by msg.payload seconds.

The idea was that I create a while loop which loops until a counter reaches the designed msg.payload value. And within this loop I create a setTimeout function that increases this internal timer advancing it by 1 second each time:

let sleepcounter = 0;
let lastcounter = -1;

while (sleepcounter < msg.payload) {
    node.warn("While "+sleepcounter+"<"+msg.payload);
    if (lastcounter !== sleepcounter) {
        lastcounter = sleepcounter;
        setTimeout(function () {
            sleepcounter++;
        }, 1000);
        node.send({"payload": sleepcounter});
        node.status({ fill: "blue", shape: "ring", text: sleepcounter });
    }
}
return msg;

I must have done something terrible. I added a node.warn also a node.send and node.status so I can see what the code is doing, but it is doing nothing. And I think also crashing node-red.
The if statement was added so the code does not call setTimeout all the time, only until the timer actual expired and it is ready to be called again.

How come I am not seeing anything on the node status or the debugger?

Why?

Because the loop happens so fast and exits. IE it is not doing what you think it is.


Truth bomb: avoid loops in functions where possible (unless you are using fixed small sized loops where they will definitely exit and not crash the node runtime). The nodejs runtime is a single threaded event loop & running loops will block the event loop causing poor performance and other side effects.


If you explain what you are doing and what you are trying to achieve we can advise.

I am building a general purpose rule engine where I define various conditions (like if a sensor value is above a certain threshold) and if the conditions are met, there are a set of instructions to perform. Maybe turn on a lamp, wait, turn it off and also do something else.
I expect the code to do something after the delay (not just sending data) this is why I wanted to keep the processing within the loop.

I know my other option would be to send the data out on a second output and loop in back to the input through a delay node. And in that case I would also need to pass the entire context so the code can pick up the processing after the delay. Not impossible to do, but I was hoping to keep it simple and be able to delay the processing inside the function node.

For this kind of thing, a delay is not ideal IMO. An alternative to consider would be generating schedules (something like cron-plus) can have dynamically generated schedules that trigger when needed (you can also programmatically set the payload it emits)

As for your rule engine, if suggest making it from standard nodes in particular the link call in dynamic mode - which can be used to dynamically branch to areas of code based on some property value.

The most important part to remember node (and thus node-red) is primarily event based. Your solution should (ideally) be built with that in mind.

Thanks for these insight. I have done scheduling before, I think I can re-use that logic. Probably save the entire context for the schedule and change my code that it can return the execution after the delay step.
Should be doable.

There is nothing wrong with using a single setTimeout within a function as you can ensure you pass the correct context to the internal function. But looping is tricky (as you found) so generally best to avoid.

For completeness, the reason your code did not work, that the code

        lastcounter = sleepcounter;
        setTimeout(function () {
            sleepcounter++;
        }, 1000);
        node.send({"payload": sleepcounter});
        node.status({ fill: "blue", shape: "ring", text: sleepcounter });

starts the timer, but then immediately sends the message and the status. You would need to put the send and status calls inside the function to make them happen after 1 second. But you would also have to restructure the whole thing to get it to do what you want. Basically this technique is to be avoided if possible. It is tricky to get it right.

I wanted it to go out immediately, and the if statement was supposed to stop setTimeout to be started again if it is already running.
if (lastcounter !== sleepcounter) should only be true initially or after the timer function incremeneted a sleepcounter. In that case a timer will be triggered again...

Ok, I see what you are trying to do. The problem is that your code will sit, hogging the processor and stopping anything else going on, running round your loop at top speed waiting for the timer to run down. The result is that everything else going on in node red stops, so the dashboard will stop, no messages will get handled.

Maybe it's something you can use?

[{"id":"f16f1303aec8dfdb","type":"function","z":"da612d68cde527a4","name":"Timeout","func":"let msg1 = {}\nlet timeout = msg.payload // sec\n\nlet MyTimeOutID = context.get('MyTimeOutID') ?? undefined\n\nmsg1 = { payload: 'Done' }\n\nfunction MyTimeOut() {\n    if (MyTimeOutID != undefined) { clearTimeout(MyTimeOutID) }\n    MyTimeOutID = setTimeout(function () {\n        clearTimeout(MyTimeOutID)\n        node.send(msg1)\n    }, 1000 * timeout)\n}\n\nMyTimeOut()","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":460,"y":1120,"wires":[["ef447c2c8430f28b"]]},{"id":"a10d62e7f60f8f58","type":"inject","z":"da612d68cde527a4","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"0","payloadType":"num","x":190,"y":1060,"wires":[["f16f1303aec8dfdb"]]},{"id":"b46aaa672e12435c","type":"inject","z":"da612d68cde527a4","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"1","payloadType":"num","x":190,"y":1100,"wires":[["f16f1303aec8dfdb"]]},{"id":"aabc209e36dcb703","type":"inject","z":"da612d68cde527a4","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"3","payloadType":"num","x":190,"y":1140,"wires":[["f16f1303aec8dfdb"]]},{"id":"d5109337a1d74ca7","type":"inject","z":"da612d68cde527a4","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"5","payloadType":"num","x":190,"y":1180,"wires":[["f16f1303aec8dfdb"]]},{"id":"ef447c2c8430f28b","type":"debug","z":"da612d68cde527a4","name":"debug 349","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":670,"y":1120,"wires":[]}]

1 Like

That can be done with a Delay node

image

[{"id":"bea323d7f318928b","type":"inject","z":"bdd7be38.d3b55","name":"0","props":[{"p":"payload"},{"p":"delay","v":"0","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Done","payloadType":"str","x":150,"y":4220,"wires":[["0fc48874801a9618"]]},{"id":"7e19a24d12238d4a","type":"inject","z":"bdd7be38.d3b55","name":"1","props":[{"p":"payload"},{"p":"delay","v":"1000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Done","payloadType":"str","x":150,"y":4260,"wires":[["0fc48874801a9618"]]},{"id":"206b347015db8685","type":"inject","z":"bdd7be38.d3b55","name":"3","props":[{"p":"payload"},{"p":"delay","v":"3000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Done","payloadType":"str","x":150,"y":4300,"wires":[["0fc48874801a9618"]]},{"id":"de6dab64a58a2255","type":"inject","z":"bdd7be38.d3b55","name":"5","props":[{"p":"payload"},{"p":"delay","v":"5000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Done","payloadType":"str","x":150,"y":4340,"wires":[["0fc48874801a9618"]]},{"id":"0fc48874801a9618","type":"delay","z":"bdd7be38.d3b55","name":"Delay","pauseType":"delayv","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":370,"y":4280,"wires":[["cbe452e6f1189943"]]},{"id":"cbe452e6f1189943","type":"debug","z":"bdd7be38.d3b55","name":"debug 2478","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":4280,"wires":[]}]

Thanks, I will check if that keeps the process running as mentioned above.