Skip to main content
A Task<T, E> is a lazy async operation that doesn’t start until you call .execute(). Under the hood it always resolves to a ResultAsync<T, E>, giving you full access to the Result API after execution. The key difference from a Promise: a Task is a description of work, not the work itself. You can attach retries, timeouts, and circuit breakers before a single network call has been made.

Creating tasks

Task.from(fn, onError)

Wraps a function that returns a Promise. The optional onError mapper converts rejected values into typed errors:

Task.ask<Context>()

Creates a task that returns the context provided via .provide(). Use this for dependency injection:

Chaining

.chain(fn)

Chains a function that returns another Task. Errors short-circuit automatically:

.map(fn) and .mapErr(fn)

Transform the value or error without creating a new async operation:

Resilience

Chain resilience operators before calling .execute(). They compose cleanly and are applied in order:

.retry(count, options)

Retries the task up to count times on failure. Pass delay (ms) and factor for exponential backoff:

.timeout(ms, onTimeout)

Fails with a custom error if the task takes longer than ms milliseconds:

.circuitBreaker({ threshold, resetTimeout })

Opens the circuit after threshold consecutive failures and rejects all calls until resetTimeout ms have elapsed:

.throttle(concurrency)

Limits the number of concurrent executions of this task:

.fallback(otherTask)

Runs otherTask if the primary task fails:

Recovery

.orElse(fn)

Recover from any error by returning a new Task:

.catchTag(errorFactory, handler)

Catch a specific tagged error variant. It is removed from the error union type after handling:

Side effects

Execution control

.delay(ms)

Waits ms milliseconds before executing:

.withSignal(abortSignal)

Cancels the task if the signal is aborted:

.once()

Executes only once and caches the result for all subsequent calls:

.memoize({ ttl? })

Like .once(), but supports a time-to-live in milliseconds after which the cache expires:

.cache(key, store)

Uses a custom cache store implementing the TaskCache interface. Useful for Redis, IndexedDB, or any external store:

Context/DI with Task.ask()

Task.ask() combined with .provide() is a lightweight dependency injection pattern that keeps your tasks testable and decoupled:

Resource management

Task.using() acquires a resource, runs a task, and guarantees the resource is released, even if the task fails:

Parallel execution

Do-notation with Task.go()

Use Task.go() to write sequential task pipelines that look imperative, without nesting .chain() calls:
Task.go() is the Task equivalent of chas.go() for Results. Both use JavaScript generators and the yield* operator as a typed short-circuit mechanism.