Async work
Three tools, and the choice is mostly about who starts the work and whether it can be cancelled.
| Runs | Started by | Cancellable | |
|---|---|---|---|
Callback<A, R> |
synchronously | you call it | — |
Action<A, R> |
async | you call it | yes |
Resource<T> |
async | its dependencies | refetches |
Callback
Section titled “Callback”A Callback<A, R> is a function that can read and write reactive state.
let add = Callback::new(move |n: i32| { *state.count().write() += n;});
add.call(5);It is Copy, so it can be stored in context and used from anywhere inside the
pier.
#[derive(Clone, Copy)]struct AddCount(Callback<i32, ()>);
// in run_pierprovide_context(AddCount(add));
// in run_telllet AddCount(add) = use_context::<AddCount>().unwrap();add.call(n);That pattern (a callback in context, invoked by a tell) is how you keep a rule in one place instead of repeating it in every runner that needs it.
Action
Section titled “Action”An Action<A, R> is a callback that awaits, and can be cancelled.
let ticker: Action<i32, ()> = Action::new(move |step| async move { loop { sleep(1_000).await; *state.count().write() += step; }});
ticker.call(1); // startticker.cancel(); // stopIt also tracks its own state:
ticker.pending(); // runningticker.ready(); // finishedAn action is the right answer when a tell needs to start something slow. The tell returns immediately, the action keeps working, and the hails watching the state it mutates update on their own.
Resource
Section titled “Resource”A Resource<T> is an async value that refetches when its dependencies
change. You do not call it.
let profile: Resource<User> = Resource::new(move || async move { let id = *user_id.read(); // dependency fetch_user(id).await});Change user_id, and the resource fetches again.
Reading gives an Option<T> (None while the first fetch is in flight):
match &*profile.read() { Some(user) => { /* ... */ } None => { /* loading */ }}Wire it to JS with set_read_hail, and declare the ret as Option<T> so the
loading state arrives as undefined:
#[ret(Option<User>)]Profile,Hail::Profile => resource.set_read_hail::<Converter>(),const profile = pier.readHail("Profile"); // User | undefinedTry it
Section titled “Try it”Both async tools in one place. The resource multiplies count by ten, after a
deliberate 600ms wait. The ticker is an action: +1 every second until
stopped.
Press +1 and watch the order of events: count updates immediately, the
resource marks itself pending, and the new value lands when the wait is over.
Nothing subscribes it to count. Reading the value inside the fetch is what
makes it follow.
import { usePier } from "../../setup/solid/bridge";
export default function ResourceDemo() { const pier = usePier(); const count = pier.readHail("Count"); const tenTimes = pier.readHail("TenTimes"); // number | undefined const loading = pier.readHail("TenTimesLoading"); const running = pier.readHail("TickerRunning"); // Action state
return ( <div class="demo"> <p> count: <b id="count">{count()}</b> · ×10 (async):{" "} <b id="ten-times">{tenTimes() ?? "—"}</b>{" "} <span id="loading">{loading() ? "(fetching…)" : ""}</span> </p> <button id="bump-1" onClick={() => pier.tell({ Bump: 1 })}> +1 </button> <button id="start" onClick={() => pier.tell({ StartTicker: 1 })}> start ticker (+1/s) </button> <button id="stop" onClick={() => pier.tell("StopTicker")}> stop </button> <span id="running">{running() ? " ticking…" : ""}</span> </div> );}import { useReadHail, useTell } from "../../setup/react/bridge";
export default function ResourceDemo() { const count = useReadHail("Count"); const tenTimes = useReadHail("TenTimes"); // number | undefined const loading = useReadHail("TenTimesLoading"); const running = useReadHail("TickerRunning"); // Action state const tell = useTell();
return ( <div className="demo"> <p> count: <b id="count">{count}</b> · ×10 (async):{" "} <b id="ten-times">{tenTimes ?? "—"}</b>{" "} <span id="loading">{loading ? "(fetching…)" : ""}</span> </p> <button id="bump-1" onClick={() => tell({ Bump: 1 })}> +1 </button> <button id="start" onClick={() => tell({ StartTicker: 1 })}> start ticker (+1/s) </button> <button id="stop" onClick={() => tell("StopTicker")}> stop </button> <span id="running">{running ? " ticking…" : ""}</span> </div> );}<script setup lang="ts">import { useReadHail, useTell } from "../../setup/vue/bridge";
const count = useReadHail("Count");const tenTimes = useReadHail("TenTimes"); // number | undefinedconst loading = useReadHail("TenTimesLoading");const running = useReadHail("TickerRunning"); // Action stateconst tell = useTell();</script>
<template> <div class="demo"> <p> count: <b id="count">{{ count }}</b> · ×10 (async): <b id="ten-times">{{ tenTimes ?? "—" }}</b> <span id="loading">{{ loading ? "(fetching…)" : "" }}</span> </p> <button id="bump-1" @click="tell({ Bump: 1 })">+1</button> <button id="start" @click="tell({ StartTicker: 1 })">start ticker (+1/s)</button> <button id="stop" @click="tell('StopTicker')">stop</button> <span id="running">{{ running ? " ticking…" : "" }}</span> </div></template><script lang="ts"> import { useReadHail, useTell } from "../../setup/svelte/bridge";
const count = useReadHail("Count"); const tenTimes = useReadHail("TenTimes"); // number | undefined const loading = useReadHail("TenTimesLoading"); const running = useReadHail("TickerRunning"); // Action state const tell = useTell();</script>
<div class="demo"> <p> count: <b id="count">{$count}</b> · ×10 (async): <b id="ten-times">{$tenTimes ?? "—"}</b> <span id="loading">{$loading ? "(fetching…)" : ""}</span> </p> <button id="bump-1" on:click={() => tell({ Bump: 1 })}>+1</button> <button id="start" on:click={() => tell({ StartTicker: 1 })}>start ticker (+1/s)</button> <button id="stop" on:click={() => tell("StopTicker")}>stop</button> <span id="running">{$running ? " ticking…" : ""}</span></div>Running now, in your browser
Three things worth trying:
- Press
+1several times quickly. Only the last fetch survives: each run cancels the one before it, so an older result never overwrites a newer one. - Start the ticker. Every tick moves
count, and the resource refetches each time. Nobody wired those together; they both just depend oncount. - Stop it. The count freezes exactly where it is, and
ticking…disappears. That indicator is a memo over the action’s ownpending()state.
Which one
Section titled “Which one”- A synchronous rule you want to reuse → Callback
- Something slow that a user starts or stops → Action
- Data that follows a value → Resource
All of these belong to a scope. Sphere and lifetime is that scope.