I knew I had done something very similar, and @Andrei reminded me. In the flow he linked to I posted a function node that does something very similar that almost does what you want.
[{"id":"5f8de1b.3f7c8a","type":"function","z":"6dc690a3.1abc88","name":"Slew rate limit","func":"// Limits the slew rate incoming payload values\nvar maxRate = 60 ; // max slew rate units/minute\nvar period = 1000; // period in millisecs to send new values, 1000 is 1 per second\n\nvar newValue = Number(msg.payload);\nvar timer = context.get('timer') || 0;\n// check the value is a number\nif (!isNaN(newValue) && isFinite(newValue)) {\n var target = msg.payload;\n context.set('target', target);\n // set last value to new one if first time through\n var lastValue = context.get('lastValue');\n if (typeof lastValue == \"undefined\" || lastValue === null) {\n lastValue = newValue;\n context.set('lastValue', newValue)\n }\n // calc new value\n msg.payload = calcOutput();\n // stop and restart the timer\n if (timer) {\n clearTimeout(timer);\n context.set('timer', null);\n }\n timer = setInterval(function(){\n // the timer has run down calculate next value and send it\n var newValue = calcOutput();\n if (newValue != context.get('lastValueSent')) {\n context.set('lastValueSent', newValue)\n node.send({payload: newValue});\n }\n },period);\n context.set('timer', timer);\n context.set('lastValueSent', msg.payload);\n} else {\n // payload is not a number so ignore it\n msg = null;\n}\nreturn msg;\n\n// determines the required output value\nfunction calcOutput() {\n var lastValue = context.get('lastValue');\n var target = context.get('target');\n // set to current value if first time through\n if (typeof lastValue == \"undefined\" || lastValue === null) lastValue = newValue;\n var now = new Date();\n var lastTime = context.get('lastTime') || now;\n // limit value to last value +- rate * time\n var maxDelta = (now.getTime() - lastTime.getTime()) * maxRate / (60 * 1000);\n if (target > lastValue) {\n newValue = Math.min( lastValue + maxDelta, target);\n } else {\n newValue = Math.max( lastValue - maxDelta, target);\n }\n context.set('lastValue', newValue);\n context.set('lastTime', now); \n return newValue;\n}","outputs":1,"noerr":0,"x":433,"y":232,"wires":[["fb203846.5494b"]]},{"id":"fb203846.5494b","type":"debug","z":"6dc690a3.1abc88","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":650.5,"y":231,"wires":[]},{"id":"df5dd6ab.d3bdd","type":"inject","z":"6dc690a3.1abc88","name":"","topic":"","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":159.5,"y":176,"wires":[["5f8de1b.3f7c8a"]]},{"id":"2a7e98bc.cd7f18","type":"inject","z":"6dc690a3.1abc88","name":"","topic":"","payload":"10","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":158,"y":237,"wires":[["5f8de1b.3f7c8a"]]},{"id":"381ece91.b0c4a2","type":"ui_slider","z":"6dc690a3.1abc88","name":"","label":"slider","group":"41737f1.c5e588","order":0,"width":0,"height":0,"passthru":false,"topic":"","min":0,"max":"10","step":1,"x":172.5,"y":304,"wires":[["5f8de1b.3f7c8a"]]},{"id":"41737f1.c5e588","type":"ui_group","z":"","name":"Default","tab":"bb9b58bd.29621","disp":true,"width":"6"},{"id":"bb9b58bd.29621","type":"ui_tab","z":"","name":"Home","icon":"dashboard"}]
Again, though, I suggest not saving the timer id in persistent store. Make sure that if you are using the local file store for context that you also provide a memory store and use that.