Cancelling a javascript countdown in function

I'm just writing a function to carry out some admin tasks, like rebooting node-RED, shutting down the pi, etc, and have a countdown which when it reaches zero, the command is passed to an exec node to fulfill the request.

reboot

I want to also add a further feature - the ability to cancel the countdown (in case I've been too hasty!!). I was intending to add a further 'Cancel' inject node, and pick up it's msg.payload in the function node, and use that to stop the countdown.

However... I'm finding it difficult to actually stop the countDown function once it has started!!
I understand that clearTimeout is supposed to cancel setTimeout, but I can't seem to get it to work (I don't think I'm implementing it correctly).
Any help would be appreciated as to how to do this.

function countDown(){
    if(n > 0){
      setTimeout(countDown,1000);
           node.status({text:(text+" in "+n+" seconds")});
           n--;
   } else {
     node.status({text:(text+" now!")});
     node.send({"payload": cmd});
   }
}
[{"id":"d9e67fbc.ddae3","type":"inject","z":"4487e413.bb781c","name":"System Shutdown","topic":"","payload":"shutdown","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":220,"y":1210,"wires":[["8c11f771.6cc238"]]},{"id":"b3dbd6a6.319368","type":"inject","z":"4487e413.bb781c","name":"System Reboot","topic":"","payload":"reboot","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":230,"y":1170,"wires":[["8c11f771.6cc238"]]},{"id":"87b651db.8fe59","type":"inject","z":"4487e413.bb781c","name":"Restart node-RED","topic":"","payload":"nrrestart","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":220,"y":1250,"wires":[["8c11f771.6cc238"]]},{"id":"8c11f771.6cc238","type":"function","z":"4487e413.bb781c","name":"Countdown","func":"var n = 10;\nsetTimeout(countDown,1000);\nconst ip = msg.payload;\nvar cmd;\nvar text;\n    if (ip == \"reboot\"){\n        cmd = \"sudo reboot\";\n        text = \"System Reboot\";\n    } else if (ip == \"shutdown\"){\n        cmd = \"sudo shutdown\";\n        text = \"System Shutdown\";\n    } else if (ip == \"nrrestart\"){\n        cmd = \"node-red-restart\";\n        text = \"NR Restart\" \n    } else if (ip == \"cancel\"){\n        // stop the coundown\n    }\n\nfunction countDown(){\n    if(n > 0){\n      setTimeout(countDown,1000);\n           node.status({text:(text+\" in \"+n+\" seconds\")});\n           n--;\n   } else {\n     node.status({text:(text+\" now!\")});\n     node.send({\"payload\": cmd});\n   }\n}","outputs":1,"noerr":0,"x":420,"y":1210,"wires":[["611156b9.772638"]]},{"id":"611156b9.772638","type":"debug","z":"4487e413.bb781c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":600,"y":1210,"wires":[]},{"id":"f2098287.c84cf","type":"inject","z":"4487e413.bb781c","name":"Cancel","topic":"","payload":"cancel","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":440,"y":1160,"wires":[["8c11f771.6cc238"]]}]

Save the id returned from setTimeout in node.context then use it to call clearTimeout

to use clearTimeout you need to have a reference to it - so when you set it you need to assign it to something.... var tout = setTimeout(... then you can clearTimeout(tout);

It's not working at all now!
I don't think I've implemented setTimeout correctly.

var n = 10;
var tout;
const ip = msg.payload;
var cmd;
var text;
    if (ip == "reboot"){
        cmd = "sudo reboot";
        text = "System Reboot";
    } else if (ip == "shutdown"){
        cmd = "sudo shutdown";
        text = "System Shutdown";
    } else if (ip == "nrrestart"){
        cmd = "node-red-restart";
        text = "NR Restart" 
    } else if (ip == "cancel"){
        // stop the coundown
        clearTimeout(tout);
    }

function countDown(){
    if(n > 0){
      tout = setTimeout(countDown,1000);
           node.status({text:(text+" in "+n+" seconds")});
           n--;
   } else {
     node.status({text:(text+" now!")});
     node.send({"payload": cmd});
   }
}

You have to store the return value in order to reuse it in clearTimeout.

E.g. save it in node.context when you get it then recall it from node.context to use it in your call to clearTimeout.

In your function code you set tout to the value returned from setTimeout - this is correct. However, when that function node runs again, var tout is recreated (that's how function nodes work, every time they're called, all your vars are brand new / empty every single time.) - so you need somewhere to store/restore tout from.

2 Likes

Aah right, I can see what you mean now Steve, thanks.
I'll have another look at this in the morning, and see if I can get this working.

I found it easier to use setInterval, and then cancel it with clearInterval (saving the state to context - thanks @Steve-Mcl)
It works OK now, a bit overkill - but interesting writing the function node :woozy_face:

reboot

[{"id":"dd52f866.edb3c8","type":"inject","z":"4487e413.bb781c","name":"System Shutdown","topic":"","payload":"sudo poweroff","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":200,"y":1510,"wires":[["8f8e8089.058fa"]],"icon":"font-awesome/fa-power-off"},{"id":"b60d313.4c49bd","type":"inject","z":"4487e413.bb781c","name":"System Reboot","topic":"","payload":"sudo reboot","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":210,"y":1550,"wires":[["8f8e8089.058fa"]],"icon":"font-awesome/fa-refresh"},{"id":"2ad017fa.99df08","type":"inject","z":"4487e413.bb781c","name":"Restart node-RED","topic":"","payload":"node-red-restart","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":200,"y":1590,"wires":[["8f8e8089.058fa"]],"icon":"font-awesome/fa-refresh"},{"id":"8f8e8089.058fa","type":"function","z":"4487e413.bb781c","name":"Countdown","func":"const input = msg.payload;\nconst cancel = false;\nvar desc;\nvar counter = 10;\n\nif (input == \"cancel\"){\n    flow.set(\"cancel\", true);\n    return;\n} else if (input == \"node-red-restart\"){\n        desc = \"Restarting node-RED\";\n    } else if (input == \"sudo reboot\"){\n        desc = \"Rebooting system\";\n    } else if (input == \"sudo poweroff\"){\n        desc = \"Shutting down system\";\n    } else {return;}\n\nvar countdown = setInterval(function(){\n  node.status({text:(desc+\" in \"+counter+\" seconds\")});\n  counter--;\n  if (flow.get(\"cancel\")===true) {\n      clearInterval(countdown);\n      node.status({text:\"Request Cancelled\"});\n      flow.set(\"cancel\", false);\n  }\n  \n  if (counter === -1) {\n    node.status({text:(desc+\" NOW!\")});\n    clearInterval(countdown);\n    node.send({\"payload\": input});\n  }\n}, 1000);","outputs":1,"noerr":0,"x":410,"y":1570,"wires":[["f560bf46.4c304"]],"icon":"font-awesome/fa-hourglass-1"},{"id":"c06193e0.c2a49","type":"inject","z":"4487e413.bb781c","name":"Cancel","topic":"","payload":"cancel","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":"","x":240,"y":1630,"wires":[["8f8e8089.058fa"]],"icon":"font-awesome/fa-exclamation-triangle"},{"id":"f560bf46.4c304","type":"exec","z":"4487e413.bb781c","command":"","addpay":true,"append":"","useSpawn":"false","timer":"","oldrc":false,"name":"System Command","x":640,"y":1570,"wires":[[],[],[]]}]
2 Likes