# Asynchronous resources and cleanup

Own loading, cancellation, retries and source intent across changing lifetimes.

## Choose the shortest correct lifetime

| Scope | Ends when | Suitable resources |
| --- | --- | --- |
| `editor.scope` | Editor disposal | Instance services and listeners |
| `editor.documentScope` | Document replacement or disposal | Saving, uploads and file queries |
| ComponentView `mount.scope` | View unmount or replacement | Charts, previews and observers |
| `scope.fork()` | Cancellation or parent disposal | One request for one input generation |

Reusing a document ID does not reuse its lifetime. `replaceDocument()` ends the previous document scope. Observe that signal instead of comparing file names alone.

## Resource leases

~~~ts
const request = editor.documentScope.acquire(
  async signal => {
    const response = await fetch('/api/preview', { signal })
    if (!response.ok) throw new Error(`HTTP ${response.status}`)
    const url = URL.createObjectURL(await response.blob())
    return { value: url, release: () => URL.revokeObjectURL(url) }
  },
  url => { console.log('Ready preview URL', url) },
)
// The host may cancel this request before replacing the document.
request.dispose()
~~~

The host implements `/api/preview`; MSFE does not supply this service. `acquire` delivers results only to current requests and releases leases arriving after cancellation. Each acquisition owns its lease. Do not let several views independently dispose the same shared object.

`scope.own(cleanup)` registers idempotent cleanup and returns an early-release function. Disposal aborts first, then releases in reverse order. Uncancelable engines still need a signal check before accepting results and cleanup of obsolete results.

## SourceDefinition.load

For attribute-bound resources, `defineSource<Attrs, Resource>({ load(attrs, signal) })` returns `{ value, release? }`. NodeViews read `status` and `resource`; they should not issue duplicate requests.

Attributes persist business identifiers. Resource values may contain resolved URLs, readonly display data or layout results. Never persist credentials or temporary object URLs in Markdown. See [Images and external resources](/en/customization/images).

## Errors and retry belong to UI

An error report does not automatically render an error state. The component owns loading, ready and error UI, readable source fallback and retry. Each retry ends the previous child scope first.

If initialization throws after allocation but before returning its lease, release partial resources too. Register cleanup immediately or use try/finally. The [chart renderer](/en/extensions/diagrams) includes observer and instance ownership.

## Writing after asynchronous completion

A live request does not imply a valid original source position. After upload, never insert using cached numeric offsets alone. Capture an anchor in a document child scope, get a fresh target on completion, then execute a command. See [the complete intent helper](/en/guide/selection).

Saving does not require replacing text with server responses. For serialized writes, retry and navigation flush, see [Reading and saving](/en/guide/saving).

## Cleanup tests

Use manually controlled promises: start A then B and complete A last; finish work after disposal; replace a document with the same ID; throw halfway through initialization. Assert no stale UI or writes, duplicate finalization or remaining observers.
