Function: asCleanup()
API / @xmachines/play-dom-router / asCleanup
function asCleanup<R>(release): Cleanup;Defined in: play/src/disposable.ts:150
Makes one release function serve as a Cleanup.
The function comes back as itself, with DISPOSE added. Nothing wraps it, so
the identity of the release holds: a caller that compares two releases, or that puts
one in a Set, sees what it saw before this protocol existed.
The release therefore must be idempotent. A scope releases the value one time, but a caller that runs the function AND lets the scope end runs it two times. Every release of this workspace already accepts that, because a teardown that runs two times is older than this protocol.
The release must be synchronous. Disposable and not AsyncDisposable: a
Disposable serves using AND await using, because await using falls back to
Symbol.dispose, while an AsyncDisposable raises a TypeError under a plain using
and forces every holder into an async function. Nothing in this workspace releases
asynchronously. An AsyncCleanup can arrive the day something does.
The R extends void | undefined bound is what holds that line. A plain () => void
parameter would ACCEPT async () => {}, because TypeScript lets a function that
returns a value stand where one returning void is wanted. The promise would then go
on the floor: the scope releases, the release looks finished, and the work runs later
with nobody able to await it. The bound rejects that at the call, and it rejects a
release that returns any other value as well.
The bound holds where the compiler knows the return type. It cannot hold where the
caller erased it: R infers any for a release typed any, such as a bare
vi.fn(), and any satisfies every constraint. A release declared () => void whose
body is async passes for the same reason — the declaration, and not the body, is what
the call site sees. Write the release inline, or type it, and the bound applies.
The release lands under DISPOSE and, when the two differ, under the
Symbol.dispose that exists at the CALL. DISPOSE resolves when the module evaluates,
so a polyfill that installs the well-known symbol later would otherwise leave the
release on the registered key while a downlevelled using reads the well-known one.
Writing both closes that window for every release this function publishes.
Type Parameters
| Type Parameter |
|---|
R extends void | undefined |
Parameters
| Parameter | Type | Description |
|---|---|---|
release | () => R | The release to publish. It must return nothing. The function is changed in place, so pass a function that this release owns: a shared or module-level function becomes a Disposable for every other holder of it. |
Returns
The same function, now also a Disposable.
Throws
TypeError - When the release is frozen or is otherwise not extensible. The protocol adds a property, and a sealed function accepts none.
Example
export function watchSignal<T>(signal: Signal.State<T>, onValue: (v: T) => void): Cleanup { const watcher = new Signal.subtle.Watcher(() => {}); watcher.watch(signal); return asCleanup(() => watcher.unwatch(signal));}