@gantryland/task
v0.5.0
Published
Minimal async task with reactive state
Readme
@gantryland/task
Minimal async task primitive with reactive state and latest-run-wins behavior.
Installation
npm install @gantryland/taskQuick Start
import { Task } from "@gantryland/task";
type User = { id: string; name: string };
const userTask = new Task<User, [string]>((id) =>
fetch(`/api/users/${id}`).then((r) => r.json()),
);
await userTask.run("42");Exports
| Export | Kind | What it does |
| --- | --- | --- |
| Task | Class | Provides reactive async state with latest-run-wins behavior. |
| TaskFn | Type | Represents the async function signature used by Task.run. |
| TaskState | Type | Represents the task state snapshot shape. |
| TaskOperator | Type | Represents a function wrapper used by task.pipe(...). |
API Reference
Task
new Task<T, Args extends unknown[] = []>(fn: TaskFn<T, Args>)| Member | Signature | Description |
| --- | --- | --- |
| getState | () => TaskState<T> | Returns the current state snapshot. |
| subscribe | (listener: (state: TaskState<T>) => void) => () => void | Subscribes to state updates and emits the current state immediately. |
| run | (...args: Args) => Promise<T> | Runs the task function and updates state. Rejects on failure or cancellation. |
| fulfill | (data: T) => T | Sets success state immediately and returns data. |
| cancel | () => void | Cancels the in-flight run, if any. |
| reset | () => void | Resets to the initial stale idle state. |
| pipe | Overloaded pipe(...operators) returning a typed Task chain | Returns a new task composed from this task function. |
TaskFn
type TaskFn<T, Args extends unknown[] = []> = (...args: Args) => Promise<T>;TaskOperator
type TaskOperator<In, Out, Args extends unknown[] = []> = (
taskFn: TaskFn<In, Args>,
) => TaskFn<Out, Args>;TaskState
type TaskState<T> = {
data: T | undefined;
error: Error | undefined;
isLoading: boolean;
isStale: boolean;
};Practical Use Cases
Example: Load on Demand
const searchTask = new Task<string[], [string]>((query) =>
fetch(`/api/search?q=${encodeURIComponent(query)}`).then((r) => r.json()),
);
await searchTask.run("term");Example: Optimistic Local Fulfill
const profileTask = new Task(async () => fetch("/api/profile").then((r) => r.json()));
profileTask.fulfill({ id: "42", name: "Local Name" });Example: Cancel Superseded Work
const reportTask = new Task(async (id: string) =>
fetch(`/api/reports/${id}`).then((r) => r.json()),
);
void reportTask.run("a");
void reportTask.run("b");Example: Derive a Piped Task
const baseTask = new Task(async (id: string) =>
fetch(`/api/users/${id}`).then((r) => r.json()),
);
const hardenedTask = baseTask.pipe(
(taskFn) => async (...args: [string]) => {
const value = await taskFn(...args);
return value;
},
);Runtime Semantics
- Starting
run(...args)clearserror, setsisLoading: true, and setsisStale: false. - If a later
runstarts before an earlier one settles, the earlier run is canceled. - Canceled runs reject with
AbortErrorand do not writeerrorto state. - Failed runs keep previous
data, normalize non-Errorthrows, and writeerror. fulfill,cancel, andresetcancel any in-flight run.getStateandsubscribeexpose immutable snapshots, not mutable internal state references.pipenever mutates the source task; it always returns a newTaskinstance.
