LiveView Integration
View Source< Handler Stacks | Up: Recipes | Index | Durable Computation >
AsyncCoroutine bridges effectful computations into Phoenix LiveView,
enabling pausable state machines, conversational flows, and long-running
operations without GenServer boilerplate.
Pattern
- Write the state machine as an effectful computation using
Yield - Start it with
AsyncCoroutine.run/2 - Handle
{AsyncCoroutine, tag, result}messages inhandle_info - Resume with user input via
AsyncCoroutine.run/3
Multi-step wizard
alias Skuld.AsyncCoroutine
defmodule MyApp.Wizard do
use Skuld.Syntax
defcomp run do
name <- Yield.yield(:get_name)
email <- Yield.yield(:get_email)
{:ok, %{name: name, email: email}}
end
endLiveView:
def mount(_params, _session, socket) do
{:ok, runner} = AsyncCoroutine.run(MyApp.Wizard.run(), tag: :wizard)
{:ok, assign(socket, runner: runner, step: :name)}
end
def handle_info({AsyncCoroutine, :wizard, %ExternalSuspend{value: :get_name}}, socket) do
{:noreply, assign(socket, step: :name)}
end
def handle_event("submit_name", %{"name" => name}, socket) do
AsyncCoroutine.run(socket.assigns.runner, name)
{:noreply, socket}
end
def handle_info({AsyncCoroutine, :wizard, %ExternalSuspend{value: :get_email}}, socket) do
{:noreply, assign(socket, step: :email)}
end
def handle_info({AsyncCoroutine, :wizard, {:ok, user}}, socket) do
{:noreply, assign(socket, user: user, step: :done)}
endCancellation
Cancel on mount or when the user navigates away:
def mount(_params, _session, socket) do
if connected?(socket) do
# Cancel any previous wizard
if socket.assigns[:runner], do: AsyncCoroutine.cancel(socket.assigns.runner)
end
...
endWith EffectLogger
Persist wizard state for resumption after disconnects:
wizard = MyApp.Wizard.run()
|> EffectLogger.with_logging()
|> Reader.with_handler(%{})
{:ok, runner} = AsyncCoroutine.run(wizard, tag: :wizard)
# Extract log from ExternalSuspend.data when yielded| Operation | Purpose |
|---|---|
AsyncCoroutine.run/2 | Start wizard (async) |
AsyncCoroutine.run_sync/2 | Start + block for first yield |
AsyncCoroutine.run/3 | Resume with input |
AsyncCoroutine.cancel/1 | Cancel wizard |
< Handler Stacks | Up: Recipes | Index | Durable Computation >