nustack

reactive.event

Event flows: React, ReactWhile, ReactForever, ReactLatest.

Module nu.core.reactive.event.

Event flows: React, ReactWhile, ReactForever, ReactLatest.

An event flow runs its body for a change: the body is handed the real key of a real notification and nothing else wakes it. That makes it the right kind for reacting to something that happened (a click, a message, a key that was written) and the wrong kind for keeping state right, because delivery is best effort: a subscription can miss a write made just after it opened, and a backend may drop a message. Neither is made up for here. A body that reads state and puts it right, whatever changed, belongs in a level flow (ReconcileReactive, WaitReactive, ForEachParReactive), which can afford re-checks precisely because it ignores the key.

All four are Control flows: they drive a mutating body under query parameters (a change subscription, a condition) and yield nothing, exactly like WhileDo / ForeverDo. A change notification is bridged into async via asyncio.Queue, so all four require an async runtime and raise from their sync _compile path. The first three take one wake per notification with no collapsing; ReactLatest collapses a backlog to its newest key, since it only ever runs the latest one.

param_slots names the consumed queries (the change subscription at slot 0, a condition where present, the names the changed key binds under); the remaining slot is the body.

A body that wants the key that changed is a lambda over it (lambda key: ...): it runs once, at construction, with a ref to the key, and the name it binds under is minted for it. A plain tree body binds nothing, unless changed_key names where to put the key for it. ReactWhile's condition is a binder slot the same way.

NameSortCallMeaning
ReactcontrolReact(change, body=None, changed_key=None)Wait for one change on the subscription, run the body once, then stop.
ReactForevercontrolReactForever(change, body, changed_key=None)Run the body on every change, unconditionally, forever.
ReactLatestcontrolReactLatest(change, body, changed_key=None, initial=False)Run the body on every change, cancelling the run a newer change makes stale.
ReactWhilecontrolReactWhile(change, condition, body, changed_key=None)Run the body on each change while the condition stays truthy.

React

Wait for one change on the subscription, run the body once, then stop.

React(change, body=None, changed_key=None)

Path nu.core.React. Kind Control, sort control, cardinality void. Arity 3 (1 required).

Binds to the change subscription, blocks until exactly one notification arrives, unbinds, and closes the subscription. The body (when present) runs once, after that single notification, before React returns.

Arguments

NameTypeDefaultMeaning
changeobjectthe change subscription to wait on (a Subscription-yielding query, e.g. OnChange).
bodyobjectNonewhat to run once the change fires: a lambda over the changed key, or a tree. Optional: leave it out to just wait for one change and do nothing.
changed_keyobjectNonename the changed key is bound under while a tree body runs. Requires a body; a lambda mints its own.

Yields

Nothing.

Notes

  • Requires a body when changed_key is given: capturing a key with nothing to run it against is meaningless.
  • Requires an async runtime; the sync path raises RuntimeError.

Undocumented: example.

ReactForever

Run the body on every change, unconditionally, forever.

ReactForever(change, body, changed_key=None)

Path nu.core.ReactForever. Kind Control, sort control, cardinality void. Arity 3 (2 required).

Binds to the change subscription once and then runs the body once per notification, with no condition to end the loop. Never returns on its own; the caller ends it by cancelling the surrounding task.

Arguments

NameTypeDefaultMeaning
changeobjectthe change subscription to wait on.
bodyobjectwhat to run on every notification: a lambda over the changed key, or a tree.
changed_keyobjectNonename the changed key is bound under while each run of a tree body lasts; a lambda mints its own.

Yields

Nothing.

Notes

  • Requires an async runtime; the sync path raises RuntimeError.

Undocumented: example.

ReactLatest

Run the body on every change, cancelling the run a newer change makes stale.

ReactLatest(change, body, changed_key=None, initial=False)

Path nu.core.ReactLatest. Kind Control, sort control, cardinality void. Arity 4 (2 required).

ReactForever with switch-latest semantics (Rx switchMap). Binds to the change subscription once and starts the body as its own task on every notification. A run still going when the next notification lands is cancelled and awaited until it has fully unwound, and only then does the body start again with the newest key. Never returns on its own; the caller ends it by cancelling the surrounding task.

Arguments

NameTypeDefaultMeaning
changeobjectthe change subscription to wait on.
bodyobjectwhat to run on every notification: a lambda over the changed key, or a tree. May never finish (a live view, a server loop); the next change is what ends it.
changed_keyobjectNonename the changed key is bound under while each run of a tree body lasts; a lambda mints its own.
initialboolFalserun the body once straight away, before any notification. That run binds no key, so the body sees whatever the caller seeded under changed_key, or an unbound name (a lambda's key reads EMPTY).

Yields

Nothing.

Notes

  • Notifications that pile up while a cancelled run is unwinding are collapsed: only the newest key runs, the backlog is not replayed.
  • Each run gets a fresh branch of the Context it started from: its own attrs key space, values shared by reference. A restarted body never sees attrs a cancelled run left behind, and attrs a run binds do not reach the caller or its siblings.
  • A body that finishes on its own is fine; the flow just waits for the next change.
  • An error raised by the body, or while a cancelled run unwinds, ends the flow and propagates. The cancellation from a restart is not an error.
  • Cancelling the flow cancels and drains the running body before the subscription is unbound and closed.
  • Requires an async runtime; the sync path raises RuntimeError.

Examples

A live view restarted whenever a select moves, drawn once up front.
Needs a real substrate behind the subscription, so it cannot run
standalone::
    ReactLatest(
        Cell.source.on_change(),
        nustd.ui.lens.browse(Cell.lens, Movies),
        initial=True,
    )
A body reading the key that changed::
    ReactForever(Cell.source.on_change(), lambda key: nu.print(key))

ReactWhile

Run the body on each change while the condition stays truthy.

ReactWhile(change, condition, body, changed_key=None)

Path nu.core.ReactWhile. Kind Control, sort control, cardinality void. Arity 4 (3 required).

Binds to the change subscription once, then on every notification checks the condition first: false stops the loop and unbinds without running the body for that notification; truthy runs the body and waits for the next change. The condition is re-evaluated fresh on every notification, not just once at the start.

The condition sees the changed key as the body does, so a loop can wait out the changes it does not care about and end on the first one it does.

Arguments

NameTypeDefaultMeaning
changeobjectthe change subscription to wait on.
conditionobjectchecked after each notification, before that turn's body runs: a lambda over the changed key, or a tree. A falsy value ends the loop.
bodyobjectwhat to run on a turn where the condition holds: a lambda over the changed key, or a tree.
changed_keyobjectNonename the changed key is bound under while that turn's tree condition and tree body run; a lambda mints its own.

Yields

Nothing.

Notes

  • The key is bound for the whole turn, condition and body alike. A lambda condition and a lambda body each read it under the name their own code mints.
  • Requires an async runtime; the sync path raises RuntimeError.

Examples

Wait for the first change to a key the caller cares about::
    ReactWhile(Space.cells.on_descendants_change("*", "prog"),
               lambda key: mine.contains(key).not_(),
               nu.Noop())

On this page