There are two different things to consider here.
createTimer( blah ...)
This creates a Timer due to execute in the future, completely independent of the rule that spawned it.
Note that because it is completely independent of any rule, rules cannot affect it either. Except if we do it this way …
fred = createTimer( blah ...)
Variable fred
here is a handle, a pointer, a reference, a communications channel to the independent Timer. Through fred
we can pass instructions, like fred.cancel
to abandon the timing.
The Timer is still independent. We can destroy the handle
fred = null
and this makes no difference to the Timer, which carries on.
Likewise, when the time comes and the Timer executes, it doesn’t do anything to the handle either. fred
remains a valid handle, pointing to a Timer that has already run.
Sometimes that has uses, we can use the handle to ask if the Timer has done its thing yet.
if (fred.hasTerminated)
So that’s how these two parts, handle and Timer, interact.
How can we make use of that in practice?
A very common technique is to use the handle as a “flag” to show this Timer is running, so that rules can find out and do different things. (We will invariably make fred
a global variable so that it survives between runs of rules)
if (fred === null) {
// no timer yet, so make one
fred = createTimer( blah ...
} else {
// Timer already running, do something else
}
But - there is a problem here. Even when the Timer has executed, it doesn’t affect fred
. This only works once.
So it is up to us to set the handle to null after use. This why you often see the equivalent of
fred = null
at the end of a Timer code block, this “lowers the flag” so any rule can easily find out if there is an active Timer.
if (fred === null) {
// no timer yet, so make one
fred = createTimer( blah ...) [
// timer actions
fred = null
]
} else {
// Timer already running, do something else
}
We could use fred.hasTerminated
instead but it gets fiddly, remembering fred
will be null before the very first time we use it anyway, and null.hasTerminated
will cause a rule-stopping error
So now our rule can start a Timer if needed, and find out if already running … then do something else.
Exactly what the something else might be is your choice, it depends what you want to do.
Perhaps this is a window-open alarm, so your rule runs on window-changed. If it changes to closed and the alarm delay is already running, you want to stop it.
if (fred === null) {
// no timer yet, so make one
fred = createTimer( blah ...) [
// ring window alarm bell
fred = null
]
} else {
// Timer already running, but window now closed
fred.cancel
// Timer killed ... but not the handle yet, remember
fred = null
// now we are back to starting point
}
Or perhaps this is a motion triggered light. If your rule re-triggers while the Timer is already running, you want to extend the delay, not cancel it.
if (fred === null) {
// no timer yet, so make one
fred = createTimer( blah ...) [
// turn light off
fred = null
]
} else {
// Timer already running, but another motion trigger
fred.reschedule( blah )
// Timer changed to a new future time
// this time we leave fred alone, it is still valid
}
An alternative approach to ‘reschedule’ that will you will often see is simply to always cancel the Timer and then make a completely new one.
// kill any old Timer
fred?.cancel
// special ? here does some magic to suppress error if fred is null
// no need to set it null, we are just about to overwrite it
fred = createTimer( blah ...) [
// turn light off
fred = null
// although if we do not check fred elsewhere
// we need not do this
]
If you copy-paste from complex examples. you will get into trouble if you do not understand them.