Scheduling: every, after, and timeout
Zolo has three scheduling constructs built into the language. All are
reserved keywords — do not use every, after, or timeout as variable
names; prefer interval, scheduled, deadline, or limit instead.
every — periodic loop
every <dur> { ... } repeats the body at the given interval. It is an infinite
loop by design: terminate it with break when the exit condition is reached.
A counter with break after 3 iterations; a running sum that stops once the
desired total is reached.
// Feature: periodic loop with `every <dur> { ... }`
// Syntax: `every <duration> { body }` — repeats the body at the given
// interval. It is a keyword (not a variable name).
// When to use: health-checks, polling, animation, metrics, any
// task that needs to run repeatedly on a fixed interval.
var tick = 0
// Runs 3 times (short interval for the demo) and exits with `break`.
every 50ms {
tick = tick + 1
print("tick #{tick}")
if tick >= 3 {
break
}
}
print("done")
// expected:
// tick #1
// tick #2
// tick #3
// done
// Another example: summing until a total is reached.
var total = 0
every 25ms {
total = total + 10
if total >= 30 {
break
}
}
print("total = {total}")
// expected: total = 30
after — one-shot firing
after <dur> { ... } schedules the block to run exactly once, after the delay.
It is fire-and-forget: it does not block the main flow. Use sleep
afterwards to keep the program alive until the callback fires.
Three after calls coexisting; firing order determined by the scheduler.
// Feature: schedule one execution with `after <dur> { ... }`
// Syntax: `after <duration> { body }` — fire-and-forget; the block
// runs **once** after the delay. It is a keyword.
// When to use: one-shot callbacks, timers, expirations, single
// reminders. Unlike `every`, it does not repeat.
print("scheduling...")
// Block runs 50ms later — does not block the main flow here.
after 50ms {
print("fired after 50ms!")
}
// Multiple `after` may coexist.
after 100ms {
print("fired after 100ms!")
}
after 25ms {
print("this one was first")
}
print("scheduled, waiting...")
// Keep the program alive long enough to see the firings.
sleep 200ms
print("end")
// expected (approximate order — depends on the scheduler):
// scheduling...
// scheduled, waiting...
// this one was first
// fired after 50ms!
// fired after 100ms!
// end
timeout — deadline cancellation
timeout <dur> { ... } returns an object { ok, value, error }. If the block
completes within the deadline, ok is true and value holds the return.
If it expires, ok is false and error is "timeout". Because the
scheduler is cooperative, cancellation only occurs at yield points (sleep, IO,
etc.).
Happy path with a return within the deadline; note on cooperative behaviour and an alternative name for the deadline variable.
// Feature: cancel work that takes too long with `timeout`
// Syntax: `timeout <dur> { body }` — returns a `Result`-like
// (`{ ok, value, error }`) with `ok = true` if it completed, or
// `ok = false, error = "timeout"` if it expired.
// When to use: network calls, uncertain IO, tasks that need a
// fail-safe. Cooperative: only fires at a yield point.
// Happy path: body finishes within the deadline.
let result_ok = timeout 100ms {
return 42
}
if result_ok.ok {
print("ok: {result_ok.value}")
} else {
print("err: {result_ok.error}")
}
// expected: ok: 42
// Timeout case: body takes longer than the limit and is interrupted.
// Note: cooperative — the body must yield (e.g. via `sleep`) for
// the deadline to fire. If it never yields, the body runs to
// completion and `ok = true`.
// SKIP: sleep inside `timeout { ... }` raises a YieldError in the
// current runtime; the failure case is therefore commented out.
//
// let result_slow = timeout 30ms {
// sleep 200ms
// return "too late"
// }
// if result_slow.ok {
// print("ok: {result_slow.value}")
// } else {
// print("err: {result_slow.error}")
// }
//
// expected: err: timeout
// IMPORTANT: `timeout` is a reserved keyword — you cannot use it
// as a variable name. Use `deadline`, `limit`, `dur`, etc. instead.
let limit = 5000
print("using '{limit}' instead of 'timeout' as the name")
Challenge
Combine every 100ms with an after 500ms that sets a stop flag. The every
should check the flag and call break when it is true.
See also