Browse documentation
Build with it / Optional tasks

Optional tasks

Schedule a whole calculation. Keep individual reads local.

Source documentationView source ↗
On this page

Documentation · Direct state access · Workers and pools

Use tasks to ask a worker to run a calculation and return a result. Tasks are not required to read a shared value. A direct .get() on an attached collection is synchronous and local.

A catalog worker filters a group of candidate products. Headphones and a keyboard start in stock; a mouse is sold out. After a stock update, the same filter returns only the keyboard.

The small dataset makes the result easy to check. Keep small filters on the main thread; use a worker when the real calculation is large enough to justify dispatch.

Follow the package installation and browser setup first. These are three files in the same directory.

Define the task#

tasks.ts

ts
import type { SharedMap } from 'zerocopy';
import { defineTasks } from 'zerocopy/worker';

export interface Model { stock: SharedMap<'number'> }
export const tasks = defineTasks<Model>()({
  findAvailable({ state }, productIds: readonly string[]) {
    return productIds.filter(id => (state.stock.get(id) ?? 0) > 0);
  },
});
export type Tasks = typeof tasks;

Expose it in the worker#

catalog.worker.ts

ts
import { serve } from 'zerocopy/worker';
import { tasks } from './tasks';

await serve(tasks);

Call it from the main thread#

main.ts

ts
import type { Tasks } from './tasks';

if (!crossOriginIsolated) {
  throw new Error('Shared worker memory requires cross-origin isolation');
}
const { SharedMap } = await import('zerocopy');
const { createState } = await import('zerocopy/state');
const { spawn } = await import('zerocopy/worker');

const state = createState({
  stock: new SharedMap('number')
    .set('headphones', 12).set('keyboard', 8).set('mouse', 0),
});
try {
  const compute = await spawn<Tasks>(
    () => new Worker(new URL('./catalog.worker.ts', import.meta.url), {
      type: 'module',
    }),
    { state },
  );
  try {
    console.log(await compute.run.findAvailable(['headphones', 'keyboard', 'mouse'])); // ['headphones', 'keyboard']
    state.update('stock', stock => stock.set('headphones', 0));
    console.log(await compute.run.findAvailable(['headphones', 'keyboard', 'mouse'])); // ['keyboard']
  } finally {
    compute.dispose(); // Disconnect and terminate the owned worker.
  }
} finally {
  state.dispose();
}

The result is Promise<string[]>. The first result is ['headphones', 'keyboard']. After the headphones sell out, it is ['keyboard']. Missing products and zero stock are excluded. The task client handles publication before dispatch; you do not write an application message protocol.

A task waits for at least the publication it requests. It can use a newer snapshot if delivery coalesces updates. Once the handler starts, its snapshot stays fixed. This is not exact invocation-time snapshot pinning.

spawn() owns the worker it creates. The example disposes the executor before the state, including after a failed call. Task arguments and ordinary results use structured cloning.

Reuse tasks.ts and catalog.worker.ts with the local, existing-worker, and pool examples. That guide also covers cancellation, shared ports, and the full API contract.

Found something unclear?Improve this guide ↗

Type a topic to find a guide.

↓ ↑ to move · Enter to open · Esc to close

Browse documentation →