Programs can set timers so that a function will be called after a certain amount of time has elapsed, and optionally repeatedly after some interval.
This is functionally equivalent to setTimeout
and setInterval
but more
expressive and works in Membrane (because Membrane will kill the javascript
process when idle)
To set a timer you use M.scheduler
, and do the following steps:
at
, on
, now
, or after
plus
or minus
every
plus
or minus
call
to specify a handlerThis sounds like a lot of steps but it's actually not that complicated.
To set a timer 5 minutes from now:
await M.scheduler.now().plus('5min').call(...);
// or, use `after` for convenience:
await M.scheduler.after('5min').call(...);
You can use plus
and minus
to "move the needle" as many times as needed:
await.M.scheduler.after('10weeks').minus('1day').plus('1hour').call(...);
The best part is that it allows you to intuitively scheduele a timer, for example, the last day of the month, using calendar math:
await M.scheduler.every('1month').minus('1day').call('handleTimer');
The interval "1 month minus one day" varies month by month, but Membrane keeps it as a series of calculation to perform every time the timer fires, as opposed to a fixed number of milliseconds, and it's thus re-evaluated each time.
Some edge cases to consider:
now()
is assumed.If you specify an interval (by calling every
), you cannot then change the
start time with plus
/minus
. Calls to plus
or minus
will be applied to
the interval instead.
As with other Membrane functions, setting a timer is an asynchronous operation
so you must await
it. The handler function can also be async
and must be
exported from index.js
. For example:
export async function update() {
await M.scheduler.after('5min').call('handleTimer');
}
export async function handleTimer(context, unschedule) {
// Handle the timer here
}
The handler is called with context
(explained below) and an unschedule
function which can be called from within the handler un schedule a repeating
timer.
To actually set a timer you must use call()
, which tells Membrane to invoke the
function with the provided name every time the timer fires:
await M.scheduler.after('2h').call('handleTimer');
Will call handleTimer
after 2 hour
Sometimes it's convenient to attach extra data when setting a timer that can be
used when the timer fires. call
takes a second parameter that will be passed
to the handler:
export async function update() {
await M.scheduler.after('2h').call('handleTimer', { time: Date.now() });
}
export async function handleTimer(context, unschedule) {
console.log('We subscribed at', context.time);
// optionally unschedule the timer
await unschedule();
}
Keep in mind that context must be something that can be serialized to JSON and
so it shouldn't contain cycles or objects that cannot be serialized (e.g.
functions).
If you need to unschedule the timer at a later time, you can use the
unschedule
parameter. Alternatively, you can use M.scheduler.unschedule
and
pass the timer's key.
You can only unschedule timers for which you have provided a key:
// Set a timer with the key 'my-timer'
await M.scheduler.at('3pm').withKey('my-timer').call(...)
At a later time you can unschedule it with:
// Unschedule the timer
await M.unschedule('my-timer');
If you need to uniquely identify a timer, use the context
parameter which
allows for more than just a string (e.g. refs!)
Sets the starting point relative to the moment is called:
await M.scheduler.after('5min').call(...);
Timer will fire in 5 minutes.
Sets the starting point to an absolute point in time:
M.scheduler.at(`3pm`).call(...)
Timer will fire at 3pm
M.scheduler.on(`tuesday`).call(...)
Timer will fire on next tuesday
Both at
and on
are equivalent but at
sounds better when used with
time-of-day (e.g. at 7pm) while on
sounds better when used with a date (e.g.
on thursday, on nov/10).
If an interval has not been set, modifies the start time by the provided amount.
If the interval has been set, modifies the interval by the provided amount.
Sets the interval to fire every time the provided interval has elapsed:
await M.scheduler.every('5min').call(...);
Or specifying a start time:
await M.scheduler.after('1day').every('5min').call(...);
You can also modify the interval using plus
and minus
await M.scheduler.after('1day').plus('5h').every('5min').minus('30s').call(...);
Keep in mind that the first call to plus
modifies the start time of the timer,
while the second one modifies the interval (because it's after every
).
Durations can be specified using an intuitive syntax: an amount followed by a unit, no spaces.
If no amount is provided, 1
is assumed so hour
and 1hour
are equivalent.
This syntax is not case sensitive.
You can use the following units when specifying a duration:
millisecond
, milliseconds
, msec
, ms
second
, seconds
, sec
, secs
, s
minute
, minutes
, mins
, min
, m
hour
, hours
, h
day
, days
, d
week
, weeks
, w
mo
, mon
, month
, months
quarter
, quarters
, q
year
, years
, y
monday
, Thurs
, 'sat'2020
, 2025
Jan
, june
01/24
, jun-13
, aug/29
2024-oct-16
, 2020/oct/10
, and even 2021/aug-5
3:30
, 11:45:15am
, 3pm
, 9:20a
, 20:11
(if no am
or
pm
is specified, 24h time is assumed)2027-12-31T08:30:30
Separators -
and /
are interchangeable.
This syntax is not case sensitive.
You can use the following as months:
january
, jan
february
, feb
march
, mar
april
, apr
may
june
, jun
july
, jul
august
, aug
september
, sep
, sept
october
, oct
november
, nov
december
, dec
You can use the following as days of the week:
monday
, mon
tuesday
, tue
, tues
wednesday
, wed
thursday
, thu
, thurs
friday
, fri
saturday
, sat
sunday
, sun