Timerslink

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:

  • Set the start time with at, on, now, or after
  • Optionally, modify the start time with plus or minus
  • Optionally, set an interval with every
  • Optionally, modify the interval with plus or minus
  • Specify a handler with call to specify a handler

This 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:

  • If no start time is set, now() is assumed.
  • If the start time is in the past, the timer will fire as soon as possible.
  • If no interval is set, the timer will only fire once.

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.

calllink

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).

withKeylink

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!)

afterlink

Sets the starting point relative to the moment is called:

await M.scheduler.after('5min').call(...);

Timer will fire in 5 minutes.

at/onlink

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).

plus/minuslink

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.

everylink

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).

Duration Syntaxlink

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

Start time syntaxlink

  • A weekday: monday, Thurs, 'sat'
  • A YYYY year: 2020, 2025
  • A month: Jan, june
  • A MM/DD date: 01/24, jun-13, aug/29
  • A YYYY/MM/DD date: 2024-oct-16, 2020/oct/10, and even 2021/aug-5
  • A time-of-day: 3:30, 11:45:15am, 3pm, 9:20a, 20:11 (if no am or pm is specified, 24h time is assumed)
  • An ISO 8601 string: 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
...