> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cbrock.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Task

> The Task<T, E> class and its instance methods.

`Task<T, E>` is a lazy, composable wrapper for async operations. Unlike a `Promise`, a Task does not start executing until you call `.execute()`. Every Task resolves to a `Result<T, E>`, giving you explicit, type-safe error handling throughout the chain.

```typescript theme={null}
import { Task } from 'ts-chas/task';
```

***

## Static factory methods

### `Task.from(fn, onError?)`

Creates a Task from a function that returns a `Promise`. The optional `onError` mapper converts any thrown error into your typed `E`.

```typescript theme={null}
const fetchUser = Task.from(
  () => fetch('/api/user/1').then(r => r.json()),
  (e) => new Error(`Fetch failed: ${e}`)
);

const result = await fetchUser.execute();
// Result<User, Error>
```

If you omit `onError`, the error type is `unknown`.

***

### `Task.ask<C>()`

Creates a Task that resolves to the context value injected via `.provide()`. Use this to declare context dependencies without passing them explicitly through every function.

```typescript theme={null}
interface AppContext {
  dbUrl: string;
}

const dbTask = Task.ask<AppContext>().chain(ctx =>
  Task.from(
    () => fetch(`${ctx.dbUrl}/records`).then(r => r.json()),
    () => new Error('DB fetch failed')
  )
);

const result = await dbTask.provide({ dbUrl: 'https://db.example.com' }).execute();
```

***

### `Task.go(generatorFn)`

Do-notation for Tasks. Lets you write sequential async logic that looks imperative, while still short-circuiting on the first `Err`.

```typescript theme={null}
const task = Task.go(async function* () {
  const user = yield* Task.from(
    () => fetch('/api/user').then(r => r.json()),
    () => new Error('fetch failed')
  );

  const profile = yield* Task.from(
    () => fetch(`/api/profile/${user.id}`).then(r => r.json()),
    () => new Error('profile fetch failed')
  );

  return { user, profile };
});

const result = await task.execute();
// Result<{ user: User; profile: Profile }, Error>
```

***

### `Task.void()`

Returns a Task that resolves to `Ok(undefined)`. Useful as a no-op or placeholder.

```typescript theme={null}
const noop = Task.void();
const result = await noop.execute(); // Ok(undefined)
```

***

### `Task.fromOption(option, onNone)`

Converts an `Option<T>` into a `Task<T, E>`. If the option is `None`, the task fails with the value returned by `onNone`.

```typescript theme={null}
import { nullable } from 'ts-chas/option';

const cached = nullable(localStorage.getItem('token'));

const task = Task.fromOption(cached, () => new Error('No cached token'));
const result = await task.execute();
// Result<string, Error>
```

***

### `Task.all(tasks)`

Runs all tasks. Resolves to `Ok` with an array of all values if every task succeeds. Short-circuits to the first `Err` encountered.

```typescript theme={null}
const tasks = [
  Task.from(() => Promise.resolve(1)),
  Task.from(() => Promise.resolve(2)),
  Task.from(() => Promise.resolve(3)),
];

const result = await Task.all(tasks).execute();
// Ok([1, 2, 3]) or Err(firstError)
```

***

### `Task.race(tasks)`

Returns the result of whichever task settles first — success or failure.

```typescript theme={null}
const result = await Task.race([
  Task.from(() => fetch('/api/primary').then(r => r.json())),
  Task.from(() => fetch('/api/mirror').then(r => r.json())),
]).execute();
```

***

### `Task.any(tasks)`

Returns the first `Ok`. Only resolves to `Err` if **all** tasks fail, in which case the error is an array of all errors.

```typescript theme={null}
const result = await Task.any([
  Task.from(() => fetchFromPrimary()),
  Task.from(() => fetchFromReplica()),
]).execute();
// Ok(firstSuccessfulValue) or Err([err1, err2])
```

***

### `Task.collect(tasks)`

Runs all tasks without short-circuiting. Resolves to `Ok` with all values if all succeed, or `Err` with **all** errors if any fail.

```typescript theme={null}
const result = await Task.collect([
  Task.from(() => validateEmail(input)),
  Task.from(() => validateAge(input)),
]).execute();
// Ok([...]) or Err([emailErr, ageErr])
```

***

### `Task.parallel(tasks, concurrency)`

Runs tasks with a bounded concurrency limit. Returns `Ok` with all values if all succeed, or the first `Err`.

```typescript theme={null}
const urls = ['/api/1', '/api/2', '/api/3', '/api/4', '/api/5'];

const tasks = urls.map(url =>
  Task.from(() => fetch(url).then(r => r.json()))
);

// Run at most 2 fetches at a time
const result = await Task.parallel(tasks, 2).execute();
// Ok([data1, data2, ...]) or Err(firstError)
```

***

### `Task.using(acquire, release, use)`

Resource management helper. Acquires a resource, passes it to `use`, then calls `release` — even if `use` fails.

```typescript theme={null}
const result = await Task.using(
  Task.from(() => openDatabaseConnection(), () => new Error('connect failed')),
  (conn) => conn.close(),
  (conn) => Task.from(() => conn.query('SELECT * FROM users'), () => new Error('query failed'))
).execute();
```

***

## Instance methods

### `.chain(fn)`

`flatMap` for Tasks. `fn` receives the `Ok` value and returns a new Task. Short-circuits on `Err`.

```typescript theme={null}
const result = await Task.from(() => Promise.resolve(1))
  .chain(n => Task.from(() => Promise.resolve(n + 10)))
  .execute();
// Ok(11)
```

***

### `.map(fn)`

Transforms the `Ok` value without changing the error type.

```typescript theme={null}
const result = await Task.from(() => Promise.resolve('hello'))
  .map(s => s.toUpperCase())
  .execute();
// Ok('HELLO')
```

***

### `.mapErr(fn)`

Transforms the `Err` value without changing the success type.

```typescript theme={null}
const result = await Task.from(
  () => Promise.reject('oops'),
  (e) => String(e)
)
  .mapErr(msg => new Error(msg))
  .execute();
// Err(Error('oops'))
```

***

### `.tap(fn)`

Runs a side effect on the `Ok` value. The result passes through unchanged.

```typescript theme={null}
const result = await Task.from(() => fetchUser())
  .tap(user => console.log('Fetched:', user.name))
  .execute();
```

***

### `.tapErr(fn)`

Runs a side effect on the `Err` value. The result passes through unchanged.

```typescript theme={null}
const result = await Task.from(() => fetchUser())
  .tapErr(err => logger.error('Fetch failed', err))
  .execute();
```

***

### `.tapTag(target, handler)`

Runs a side effect when the error matches a specific tagged error, without altering the result. Accepts either an error factory from `defineErrs` or a tag string.

```typescript theme={null}
const result = await fetchUser()
  .tapTag(AppError.NotFound, e => {
    metrics.increment('user.not_found');
  })
  .execute();
```

***

### `.orElse(fn)`

Recovery operator. If the Task fails, `fn` receives the error and returns a new Task to run instead.

```typescript theme={null}
const result = await fetchUserFromApi()
  .orElse(err => fetchUserFromCache())
  .execute();
```

***

### `.fallback(otherTask)`

Semantic alias for `.orElse(() => otherTask)`. Use when you always want to fall back to a fixed Task regardless of the error.

```typescript theme={null}
const result = await fetchUserFromApi()
  .fallback(Task.from(() => getCachedUser()))
  .execute();
```

***

### `.catchTag(target, handler)`

Catches a specific tagged error, handles it, and removes it from the error union type. The remaining union type is narrowed automatically.

```typescript theme={null}
// fetchUser returns Task<User, NotFoundErr | UnauthorizedErr>
const result = await fetchUser()
  .catchTag(AppError.NotFound, e =>
    Task.from(() => Promise.resolve(defaultUser))
  )
  .execute();
// Task<User, UnauthorizedErr>
```

***

### `.provide(context)`

Injects a context value for `Task.ask()` chains. Call this before `.execute()`.

```typescript theme={null}
const task = Task.ask<{ baseUrl: string }>().chain(ctx =>
  Task.from(() => fetch(ctx.baseUrl + '/data').then(r => r.json()))
);

const result = await task.provide({ baseUrl: 'https://api.example.com' }).execute();
```

***

## Execution

### `.execute(signal?)`

Runs the task and returns `Promise<Result<T, E>>`. Accepts an optional `AbortSignal` to cancel execution.

```typescript theme={null}
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);

const result = await task.execute(controller.signal);

if (result.isOk()) {
  console.log(result.value);
} else {
  console.error(result.error);
}
```

***

### `.unwrap()`

Runs the task and returns `Promise<T>`. Throws if the result is `Err`. Use only when you're certain the Task cannot fail.

```typescript theme={null}
const value = await Task.from(() => Promise.resolve(42)).unwrap();
// 42
```

***

## Resilience

These operators let you build fault-tolerant tasks. See [Resilience Patterns](/api/task/resilience) for full details and examples.

| Method                     | Description                                                |
| -------------------------- | ---------------------------------------------------------- |
| `.retry(count, options?)`  | Retry on failure, with optional exponential backoff        |
| `.timeout(ms, onTimeout)`  | Fail if the task takes longer than `ms` milliseconds       |
| `.circuitBreaker(options)` | Open the circuit after too many consecutive failures       |
| `.throttle(n)`             | Limit concurrent executions to `n`                         |
| `.delay(ms)`               | Wait `ms` milliseconds before executing                    |
| `.withSignal(signal)`      | Bind an `AbortSignal`; fail immediately if already aborted |

***

## Caching

### `.once()`

Executes the task once and caches the result forever. Subsequent calls return the cached `Result` without re-running.

```typescript theme={null}
const initTask = Task.from(() => loadConfig()).once();

// Both calls resolve to the same Result; the fetch only happens once
await initTask.execute();
await initTask.execute();
```

***

### `.memoize(options?)`

In-memory memoization with an optional TTL. By default, only successful results are cached.

```typescript theme={null}
const userTask = Task.from(() => fetchUser()).memoize({ ttl: 60_000 });

await userTask.execute(); // fetches
await userTask.execute(); // returns cached result (within 60s)
```

| Option     | Type      | Description                                                 |
| ---------- | --------- | ----------------------------------------------------------- |
| `ttl`      | `number`  | Cache lifetime in milliseconds. If omitted, caches forever. |
| `cacheErr` | `boolean` | If `true`, errors are also cached. Default: `false`.        |

***

### `.cache(key, store, options?)`

External cache via any object that implements `TaskCache`. Useful for Redis, local storage, or shared application state.

```typescript theme={null}
const store = new Map<string, any>();

const result = await Task.from(() => fetchUser())
  .cache('user:1', store, { ttl: 30_000 })
  .execute();
```

***

## Context

### `.context(ctx)`

Attaches debug information to the error if the Task resolves to `Err`. The `_context` array on the error shows the most recent annotation first. Useful for tracing which step in a chain failed.

```typescript theme={null}
const result = await Task.from(() => fetchUser())
  .context('fetching user for dashboard')
  .execute();

if (result.isErr()) {
  console.log(result.error._context); // ['fetching user for dashboard']
}
```

Accepts either a string or a plain object.

```typescript theme={null}
.context({ step: 'fetch-user', userId: '123' })
```

***

## `TaskCache` interface

Implement this interface to use any storage backend with `.cache()`.

```typescript theme={null}
interface TaskCache {
  get<T>(key: string): T | undefined | Promise<T | undefined>;
  set<T>(key: string, value: T, ttl?: number): void | Promise<void>;
}
```

A plain `Map` satisfies this interface. For external stores, implement both methods:

```typescript theme={null}
const redisStore: TaskCache = {
  get: async (key) => {
    const raw = await redis.get(key);
    return raw ? JSON.parse(raw) : undefined;
  },
  set: async (key, value, ttl) => {
    const serialized = JSON.stringify(value);
    if (ttl) {
      await redis.set(key, serialized, 'PX', ttl);
    } else {
      await redis.set(key, serialized);
    }
  },
};

const result = await Task.from(() => fetchUser())
  .cache('user:1', redisStore, { ttl: 60_000 })
  .execute();
```
