Capability: declare a triggerable async task that produces a value into an assigns field.
Structurally an async :foo do ... end block parallels
messages do message :foo do ... end end: a named block holds a
sequence of ops, and the runtime walks them in order.
Today the body is restricted to a single run fn assigns -> ... end
op — the work to perform. The runtime wraps the result in a
%Phoenix.LiveView.AsyncResult{} on the named field.
Shape
async :report do
run fn assigns ->
{:ok, SlowService.fetch_report(assigns.user_id)}
end
endReturning {:ok, value} from the run fn produces
AsyncResult.ok(value). Returning a raw value also produces
AsyncResult.ok(value) — the wrapper is a convenience for
callers that want the explicit shape. Raises / exits produce
AsyncResult.failed/2.
Firing
An async declaration on its own DOES NOTHING. It just
registers a named computation. To actually fire it, use the
fire :foo op inside a lifecycle block (mount do), an action
body, or a message body. This decouples the work definition
from the trigger — three trigger paths (mount, action, message)
all use the same fire op against the same declaration.
Layer
Layer 1: an async declaration is plain Elixir wrapped in
%AsyncResult{} plumbing. No reactive graph involvement. For
reactive auto-recompute on dep change, see
calculate :foo, rx(...), async: true (layer 2).
Summary
Functions
Top-level async :name do <ops> end declaration. Registers a
named, triggerable async task. The body is a sequence of ops;
today only a single run fn assigns -> ... end is supported.
Functions
Top-level async :name do <ops> end declaration. Registers a
named, triggerable async task. The body is a sequence of ops;
today only a single run fn assigns -> ... end is supported.
The named field lands on assigns as a %Phoenix.LiveView.AsyncResult{}.
Before the first fire it is AsyncResult.loading() with
loading == nil (i.e. it's an empty default); after fire :name
it transitions to loading() |> with loading: [name], then
resolves to ok(value) or failed(reason).