[API reference](https://evolu.dev/docs/api-reference) › [@evolu/common](https://evolu.dev/docs/api-reference/common) › [Task](https://evolu.dev/docs/api-reference/common/Task) › callback

```ts
function callback<T, E, D>(
  fn: (options: {
    reject: (defect: unknown) => void;
    resolve: (result: Result<T, E>) => void;
    run: Run<D>;
  }) => void | (() => void),
): Task<T, E, D>;
```

Defined in: [packages/common/src/Task.ts:3740](https://github.com/evoluhq/evolu/blob/037c390af081e9944d616ff298a729515c5c5ab7/packages/common/src/Task.ts#L3740)

Creates a [Task](https://evolu.dev/docs/api-reference/common/Task/type-aliases/Task) from a callback-based API.

Use this to wrap callback-style APIs such as event listeners and Node.js
callbacks. Resolve with `ok(value)` or `err(error)`, or reject with a defect,
to complete the Task. Settlement is one-shot: the first `resolve` or `reject`
wins, and later settlement calls are ignored, matching Promise semantics.
When `reject` wins settlement, the defect panics the [Run](https://evolu.dev/docs/api-reference/common/Task/interfaces/Run) tree and is
observed at [Fiber](https://evolu.dev/docs/api-reference/common/Task/interfaces/Fiber) boundaries as [AbortError](https://evolu.dev/docs/api-reference/common/Task/variables/AbortError) with
[PanicAbortReason](https://evolu.dev/docs/api-reference/common/Task/interfaces/PanicAbortReason). Rejecting AbortError is reserved for Task abort
control flow: direct `run(task)` rejects with it, and `run.abortable(task)`
returns it as an [Err](https://evolu.dev/docs/api-reference/common/Result/interfaces/Err).

This helper is a callback bridge. If `reject` forwards an Error created in a
separate async chain, V8 cannot reconstruct the caller's zero-cost async
stack through this bridge. Prefer native promise APIs and `await` when the
wrapped API already returns a Promise.

One-shot settlement applies only to `resolve` and `reject`. A synchronous
throw from the setup function is a defect that panics the Run tree even after
`resolve` was called — setup throws are bugs and must not be masked by an
earlier resolution.

Optionally return a synchronous cleanup callback. It runs when the Task Run
signal aborts, including normal completion and explicit abort. The callback
must not throw. Cleanup defects panic the root Run; if the callback Task
already settled, its Fiber keeps the original [Result](https://evolu.dev/docs/api-reference/common/Result/type-aliases/Result) while the root
still reports the panic. Cleanup must be synchronous; returned promises are
not awaited. For async cleanup, prefer [acquireUseRelease](https://evolu.dev/docs/api-reference/common/Task/functions/acquireUseRelease), `await
using` with [AsyncDisposableStack](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncDisposableStack), or a Task that owns the resource
explicitly.

If setup can throw after acquiring any resource, use a local
[DisposableStack](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DisposableStack) before returning cleanup. Register each cleanup as
soon as the resource is acquired, then move the disposer and return a cleanup
callback. If setup throws before cleanup is returned, the local `using`
disposal releases already-acquired resources.

### Example

```ts
import {
  assertEqual,
  assertOk,
  callback,
  createRun,
  ok,
  type Task,
} from "@evolu/common";

const listeners = new Set<(message: string) => void>();
const subscribe = (listener: (message: string) => void): (() => void) => {
  listeners.add(listener);
  return () => {
    listeners.delete(listener);
  };
};
const nextMessage: Task<string> = callback(({ resolve }) =>
  subscribe((message) => resolve(ok(message))),
);

await using run = createRun();
const fiber = run(nextMessage);
assertEqual(listeners.size, 1);
for (const listener of listeners) listener("ready");
assertOk(await fiber, "ready");
// The callback cleanup unsubscribes after settlement.
assertEqual(listeners.size, 0);
```