Awaiting Events
await <event source> suspends the current frame inside an async function until the event’s next firing, then returns the payload. The wait is essentially a one-shot subscription: it resumes on firing, and resuming deregisters it. It produces no subscription handle and does not interfere with ~> subscriptions.
The Result = the Handler’s Parameter List
await’s result is packaged by the same rules as the parameters the event’s handler receives: whatever the handler gets, await produces.
| Event declaration | Plain await result | With cancel / timeout |
|---|---|---|
Zero-parameter event Started(); | void (statement form only) | bool (true = fired, false = cancelled or timed out) |
Single-parameter event Solo(x: int); | int (bare value) | int? |
Multi-parameter event Pair(a: int, b: string); | (int, string) tuple | (int, string)? |
Event prop event prop Level: int | (int, int) (old, new) | (int, int)? |
event Solo(x: int);
event Pair(a: int, b: string);
event Started();
async func main() -> int
{
spawn { await 5ms; emit Solo(7); };
val v = await Solo;
print("solo:", v);
spawn { await 5ms; emit Pair(1, "a"); };
val (x, y) = await Pair;
print("pair:", x, y);
spawn { await 5ms; emit Started(); };
await Started;
print("started");
return 0;
}solo: 7
pair: 1 a
started- A multi-parameter result is a tuple; the call site destructures it directly.
- A zero-parameter event has no payload and can only be awaited as a statement.
The Cancellation Channel Makes the Result Nullable
An await with cancel ct or timeout D makes the result nullable: at the moment of cancellation or timeout the frame keeps running onward, the await expression’s value is null, and defer still runs.
timeout amounts to an automatic ct: the deadline cancels when it arrives, and if the event arrives first you get the real value — first to arrive wins. Pre-cancellation (the token already cancelled before the await) completes immediately with null and registers no wait.
import Async.*;
event Tick(n: int);
event Ping();
async func main() -> int
{
spawn { await 20ms; emit Tick(9); };
val q: int? = await Tick timeout 5ms;
print(q == null);
val r: int? = await Tick timeout 5s;
print(r == 9);
val cts = new CancellationTokenSource();
cts.Cancel();
val b: bool = await Ping cancel cts.Token;
print(b);
return 0;
}True
True
FalseA plain await (no channel) has a non-nullable result and no cancellation wakeup: spawn cancel ct only sets the cancellation flag on a frame sleeping in an event wait without waking it, exactly mirroring await Duration. Whether cancellation happened is yours to check via ct.IsCancellationRequested (cancel.requested inside a cancellable body).
Awaiting an event that never fires suspends forever, and progress is decided by the host pump — that is the point of “awaiting an event that may never come”.
Await Targets
The operand mirrors subscription position: a declared name, a member event, an event prop, a locally held event value, or eventof(...) can all be await targets.
For event props and computed, the result is always (old, new), emitted automatically by the setter pipeline, matching the handler’s parameter convention:
event Solo(x: int);
class Door
{
public event Opened(at: int);
public func Open()
{
emit Opened(3);
}
}
class Box { public event prop Level: int { get; set; } }
async func main() -> int
{
val b = new Box();
val lev = eventof(b.Level);
spawn { await 5ms; b.Level = 42; };
val (o, n) = await lev;
print(o, n);
val d = new Door();
spawn { await 5ms; d.Open(); };
val at = await d.Opened;
print(at);
val ev = Solo;
spawn { await 5ms; emit Solo(7); };
print(await ev);
return 0;
}0 42
3
7A nullable result with a channel is consumed through a null-check branch; null narrowing works on nullable tuples, and the branch destructures directly:
event Pair(a: int, b: string);
async func main() -> int
{
spawn { await 5ms; emit Pair(1, "a"); };
val pay: (int, string)? = await Pair timeout 1s;
if (pay != null)
{
val (m, n) = pay;
print(m, n);
}
return 0;
}1 aResume Timing and Semantics
emit first runs every subscribed handler synchronously (including pipeline finally), and then resumes waiting frames one by one, FIFO in registration order: handlers observe the event synchronously, awaiters wake afterwards. Multiple frames waiting on the same event are each delivered independently, in registration order:
event Hit(n: int);
func H(n: int) -> void
{
print("h", n);
}
async func main() -> int
{
Hit ~> H;
spawn { val n1 = await Hit; print("w", 1, n1); };
spawn { val n2 = await Hit; print("w", 2, n2); };
await 10ms;
emit Hit(7);
return 0;
}h 7
w 1 7
w 2 7- No buffering: only the next firing after the await counts; firings before it are never replayed.
- One-shot: every await is an independent one-shot subscription, deregistered on resume; multiple awaits of the same event in one frame each wait independently.
- Not a subscription: it produces no subscription handle and leaves the
Cancel/IsCancelled/TriggerCounttrio untouched (see Subscription Handles and Event Values). awaitcan take ameanwhile { … }block: the wait is armed first, then the block runs inline; emitting the event inside the block takes the self-trigger fast path with no suspension at all. See the meanwhile block.- Position rules unchanged: await only works inside an async function or a spawn body; an event handler lambda body cannot await.
- Host-side emit bridges wake waiting frames too: “the host fires the event, the script awaits it” holds.
Notes
- Cancellation is a zero-error path: cancellation and timeout report no error and throw no exception — the result is simply null, and a zero-parameter event yields false.
tweenwrites components directly, bypassing the setter pipeline, so it triggers no event prop notification; awaiting an event prop only receives (old, new) pairs caused by script assignments.
