---
title: Async values
description: Load data with an async computed, and show it with @try, @pending, and @catch.
sidebar: { label: Async }
---

A handle points at an element as soon as the element is on the page. Data from the network takes longer, so the page needs a plan for the wait.

Give `computed()` an async function. Then put every read of it inside `@try`. Add `@pending` for the wait and `@catch` for a failure.

At build time, the compiler finds the async recipe and the three blocks. In the browser, the page shows `@pending` first. Your function loads the data, and the `@try` content replaces `@pending`.

If you render pages on a server, as `@markless/router` apps do, one thing changes. Fast data arrives with the page. Slow data sends `@pending` first, and the data follows in the same response.

Click **Load the page** in the figure.

<StateAsyncFigure />

Notice that the reader sees `@pending` first and the data second, in the browser and on a server.

```tsrx Greeting.tsrx
import { computed, state } from '@markless/core';
import { loadGreeting } from './greeting.ts';

export default function Greeting() @{
	let name = state('Ada');
	const details = computed(async ({ signal }) => {
		const who = name;
		return { title: await loadGreeting(who, signal) };
	});

	<section>
		@try {
			<p>{details.title}</p>
		} @pending {
			<p>Loading</p>
		} @catch {
			<p>Could not load</p>
		}
	</section>
}
```

Inside `@try`, read the result directly: `details.title`. `loadGreeting` is your own function in a `.ts` file.

When `name` changes, the computed runs again. Markless cancels the old run through `signal`, so pass `signal` to your fetch. The old text stays on screen while the new data loads. If the wait gets long, `@pending` can show again.

:::warning[Read state before the first await]
Read state at the top, before any `await`, as `const who = name` does. A state read after `await` gives `MARKLESS_ASYNC_POST_AWAIT_READ`.
:::

:::danger[Why does the build fail when I show my async value?]
Every read of an async value in markup needs a `@try` around it. A read outside one gives `MARKLESS_ASYNC_BOUNDARY_REQUIRED`.
:::

**Next:** How does a choice like `theme` survive a page reload? [Storage →](/state/storage)
