zena:task

zena
import {…} from 'zena:task';

zena:task — operations as values (docs/design/cancellation.md; PLAN.md, async roadmap).

An operation that can be started is a value, Op<T> = () => Future<T>, and this module is the algebra over such values: combinators from Op<T> to Op<T>, so composition order is syntax: retry(3, timeout(milliseconds(100), op)) gives every attempt its own 100ms budget, timeout(milliseconds(500), retry(3, op)) one budget over all of them.

Two rules make the algebra sound. Policies act on FAILURES and never on cancellation: an ambient cancel passes through every combinator untouched — catch cannot observe the cancellation channel, so retrying cancelled work is not even expressible here — because a retry after cancel would outlive the scope tree that cancelled it. And racing combinators start their candidates inside a TaskGroup, so a loser or an expired attempt is actually cancelled rather than left running (the thing a resilience layer over bare promises cannot do).

An Op is called once per attempt: the operation must be a future-RETURNING function, not a future. A future is work already started — it cannot be attempted again.

The planned Task class — an operation's observable lifecycle (initial, pending, complete, error), in the style of @lit/task — belongs here too: a Task runs an Op, and these combinators are the policies a Task runs it under.

Classes

TimeoutError

zena
class TimeoutError extends Error
Extends Error

How timeout and deadline report an attempt that ran out of budget. Carries the budget so a retry layer's log can say which.

Constructors
zena
new()
#
Properties
zena
budget: Duration
#
2 inherited members
From Error
Properties
zena
message: String
#
Methods
zena
getStackTrace(): String | null
#

TaskState

zena
sealed class TaskState<T>

Where a task's latest run stands. Cancellation is not a state: a cancelled run restores the state the task held before it (docs/design/task.md, "The state, as data").

Variants
zena
case Initial
#
zena
case Pending
#
zena
case Complete
#
zena
case Errored
#

Initial

zena
final class Initial<T> extends TaskState<T>
Extends TaskState<T>

No run has settled yet.

4 inherited members
From TaskState
zena
case Initial
#
zena
case Pending
#
zena
case Complete
#
zena
case Errored
#

Pending

zena
final class Pending<T> extends TaskState<T>
Extends TaskState<T>

A run is in flight. Deliberately carries no future: only the latest run's outcome ever becomes the state, and a held future could outlive its run's relevance.

4 inherited members
From TaskState
zena
case Initial
#
zena
case Pending
#
zena
case Complete
#
zena
case Errored
#

Complete

zena
final class Complete<T>(value: T) extends TaskState<T>
Extends TaskState<T>

The latest settled run produced this value.

Properties
zena
value: T
#
4 inherited members
From TaskState
zena
case Initial
#
zena
case Pending
#
zena
case Complete
#
zena
case Errored
#

Errored

zena
final class Errored<T>(error: Error) extends TaskState<T>
Extends TaskState<T>

The latest settled run failed with this error.

Properties
zena
error: Error
#
4 inherited members
From TaskState
zena
case Initial
#
zena
case Pending
#
zena
case Complete
#
zena
case Errored
#

Task

zena
class Task<T>

Runs an operation and exposes where the run stands — Initial, Pending, Complete, or Errored — across however many runs, latest-wins (docs/design/task.md).

The op is fixed at construction with its policies already composed: new Task(retry(3, timeout(milliseconds(100), fetchUser))). Each run starts the op inside a child CancelScope, so a superseding run() cancels the previous attempt's whole subtree rather than racing it for the state.

Constructors
zena
new(op: Op<T>, onChange: (() => void)? = null)
#
Properties
zena
state: TaskState<T> { get; }
#

Where the latest run stands.

zena
completed: Future<T> { get; }
#

The latest run's eventual answer: the value or failure of whichever run is current when one settles. Supersession carries it forward to the new run; a run started after a settle mints a fresh one.

Methods
zena
changed(): Future<void>
#

Settles at the next state transition after the call.

zena
run(): Future<T>
#

Starts a run, cancelling a pending previous one — superseded before it arrived, its outcome discarded. Returns this run's future, which settles the way the run does even if superseded.

zena
cancel(): void
#

Cancels a pending run; the previous settled state is restored when the cancellation lands. A task with no run pending is unchanged.

Type aliases

Op

zena
type Op<T> = () => Future<T>

An operation that can be attempted. Each call starts one fresh attempt and hands back its future.

Functions

timeout

zena
function timeout<T>(budget: Duration, op: Op<T>): Op<T>

Bounds each attempt to budget of wall time: the attempt races its budget, and whichever loses is cancelled. An attempt that exceeds the budget fails with TimeoutError — a failure like any other, so an outer retry treats a timeout as retryable.

deadline

zena
function deadline<T>(at: Instant, op: Op<T>): Op<T>

Bounds each attempt to finish before an absolute point on the monotonic clock (monotonic()), however many layers retry it: where timeout gives every attempt a fresh budget, a deadline spends one budget across all of them. An attempt starting past the deadline fails immediately, without running the operation.

retry

zena
function retry<T>(attempts: i32, op: Op<T>, firstDelay: Duration = milliseconds(0)): Op<T>

Runs up to attempts attempts, handing back the first value; the last attempt's failure propagates. With firstDelay, waits that long before the second attempt and doubles the wait each time after.

Retrying is the reason Op is a function: each attempt is a fresh call. Cancellation is not retried — see the module comment.

fallback

zena
function fallback<T>(op: Op<T>, alt: Op<T>): Op<T>

Tries op; on failure, answers with alt instead. The failure that triggered the fallback is discarded — when it matters, catch it yourself. Cancellation is not a failure: if op's attempt completes cancelled, the unwind passes through and alt never runs.

hedge

zena
function hedge<T>(delay: Duration, op: Op<T>): Op<T>

Starts a second attempt if the first has not settled within delay, and hands back whichever settles first, value or failure — the tool for an operation that is usually fast and occasionally very slow. The loser is cancelled. A hedge trims slowness and does not mask errors; layer retry on top for that.