Skip to content

Suspense

Primitives behind PythonNative's async rendering model: the Suspend signal, the standard asyncio tasks that run async def component bodies, cached async values (Resource / start_resource), and code splitting with lazy.

The user-facing pieces are the Suspense boundary component (documented with the other components) and the use_resource hook (documented with the other hooks); this page covers the underlying machinery.

Suspense resources backed by standard asyncio tasks.

Also home to run_eagerly, the driver behind async def components: it steps a coroutine once synchronously and only hands the remainder to a task when the first await is really pending, so bodies whose awaits are already resolved render inline without a fallback flash.

Classes:

Name Description
Suspend

Signal that a render is blocked on pending async work.

Resource

A cached async value with Suspense integration.

Functions:

Name Description
start_resource

Start fetcher immediately and wrap it in a Resource.

run_eagerly

Start coro now and return a future for its result.

lazy

Define a component that loads its implementation on first render.

Suspend

Suspend(
    waitable: Any, hook_state: Any = None, label: str = ""
)

Bases: BaseException

Signal that a render is blocked on pending async work.

Raised while a component renders, either by Resource.read or by an async def component body blocking on a pending await. The reconciler catches it: the nearest Suspense boundary shows its fallback (initial mounts), or the component keeps its previous content and re-renders when the work finishes (updates).

Derives from :class:BaseException so ErrorBoundary (which catches :class:Exception) never mistakes a suspension for a crash.

Attributes:

Name Type Description
waitable

The pending work; exposes done() and add_done_callback(cb).

hook_state

The suspended component's hook state, carried so a Suspense boundary can preserve it across retries (its cached resources survive, so the retry doesn't refetch).

key Optional[Tuple[int, Any]]

(component identity, element key) used to re-match hook_state on retry.

label

Component name for diagnostics.

Resource

Resource(driver: Future[T])

Bases: Generic[T]

A cached async value with Suspense integration.

Returned by use_resource. A resource starts fetching as soon as the hook runs and remembers its result across renders (until its dependencies change), so re-renders never refetch.

Two ways to consume it:

  • resource.read() in a regular component: returns the value when ready, re-raises the fetcher's error if it failed, and suspends the render while pending.
  • await resource in an async def component: same semantics, expressed as a plain await.

Methods:

Name Description
read

Return the fetched value, or suspend the render while pending.

cancel

Cancel the in-flight fetch (no-op when already done).

Attributes:

Name Type Description
ready bool

Whether the fetch has finished (successfully or not).

ready property

ready: bool

Whether the fetch has finished (successfully or not).

read

read() -> T

Return the fetched value, or suspend the render while pending.

Raises:

Type Description
Suspend

While the fetch is still in flight (caught by the reconciler, never by user code).

BaseException

Whatever the fetcher raised, re-raised so an enclosing ErrorBoundary can catch it.

cancel

cancel() -> None

Cancel the in-flight fetch (no-op when already done).

start_resource

start_resource(fetcher: Callable[[], Any]) -> Resource[Any]

Start fetcher immediately and wrap it in a Resource.

The fetcher may be an async def (typical) or a plain function; synchronous results resolve the resource immediately, so reading it never suspends.

This is the non-hook constructor used for module-level resources (preloading data before a screen mounts) and by lazy. Inside components, prefer use_resource, which caches per component instance and re-fetches when dependencies change.

run_eagerly

run_eagerly(
    coro: Coroutine[Any, Any, T], scope: Any
) -> Future

Start coro now and return a future for its result.

The first step of the coroutine runs synchronously, in a copy of the caller's context (so provider values and the installed hook state survive later await boundaries, exactly as in an :class:asyncio.Task). If the body finishes in that step, the returned future is already done; if it suspends, the rest of the body is driven by a task owned by scope and the returned object is that task.

Parameters:

Name Type Description Default
coro Coroutine[Any, Any, T]

The coroutine to drive.

required
scope Any

The TaskScope that owns the remaining work; cancelling the returned future cancels the body.

required

Raises:

Type Description
RuntimeError

When scope is already closed.

lazy

lazy(loader: Callable[[], Any]) -> Callable[..., Any]

Define a component that loads its implementation on first render.

loader runs once, the first time the returned component renders; until it resolves, renders suspend (so wrap usages in a Suspense boundary to show a loading state). The loaded value must be a component (a @component function or any element factory).

Parameters:

Name Type Description Default
loader Callable[[], Any]

A zero-arg callable returning the component, or an async def resolving to it. Synchronous loaders (a deferred import) resolve immediately and never suspend.

required

Returns:

Type Description
Callable[..., Any]

A component. Props (and children) pass through to the loaded

Callable[..., Any]

component unchanged.

Example
import pythonnative as pn

Chart = pn.lazy(lambda: __import__("app.chart", fromlist=["Chart"]).Chart)

@pn.component
def Dashboard():
    return pn.Suspense(
        Chart(points=[1, 2, 3]),
        fallback=pn.ActivityIndicator(),
    )

Next steps

  • Walk through async components, Suspense, and use_resource end-to-end: Async + data guide.
  • See how suspension threads through the render pass: Architecture.