Skip to main content

Composing Tasks

Chaining and combining tasks

API

Overview

Tasks are designed to be composed together. Multiple operations can be chained without triggering execution until Task.run is called.

Transforming Values

Task.map

Transform the success value of a task:

import { Task } from '@w5s/task';

const numberTask = Task.resolve(42);
const stringTask = Task.map(numberTask, (n) => `The answer is ${n}`);

await Task.run(stringTask); // Result.Ok('The answer is 42')

Task.andThen

Chain tasks that depend on previous results:

import { Task } from '@w5s/task';

const fetchUser = (id: string) => Task.create(async () => {
// ... fetch user
return Task.ok({ id, name: 'John' });
});

const fetchPosts = (userId: string) => Task.create(async () => {
// ... fetch posts
return Task.ok([{ title: 'Post 1' }]);
});

const userWithPosts = Task.andThen(
fetchUser('123'),
(user) => Task.map(
fetchPosts(user.id),
(posts) => ({ ...user, posts })
)
);

Task.andRun

Chain tasks when and the previous value:

import { Task } from '@w5s/task';

const logStart = Task.create(() => Task.ok(console.log('Starting...')));
const mainTask = Task.create(() => Task.ok('result'));
const logEnd = Task.create(() => Task.ok(console.log('Done!')));

const workflow = Task.andRun(
Task.andRun(logStart, mainTask),
logEnd
);

Handling Errors

Task.mapError

Transform the error type of a task:

import { Task } from '@w5s/task';

const failingTask = Task.reject('string error');
const mappedError = Task.mapError(failingTask, (err) => new AnotherError(err));

Task.orElse

Recover from errors:

import { Task } from '@w5s/task';

const failingTask = Task.reject(new NetworkError());
const recovered = Task.orElse(failingTask, (error) => {
console.log('Recovering from:', error);
return Task.resolve('fallback value');
});

Task.mapResult

Transform both success and error at once:

import { Task } from '@w5s/task';

const task = Task.resolve(42);
const mapped = Task.mapResult(task, (result) => {
if (result.ok) {
return Result.Ok(result.value * 2);
}
return Result.Error(new WrappedError({ cause: result.error }));
});

Parallel Execution

Task.all

Run multiple tasks in parallel and collect all results:

import { Task } from '@w5s/task';

const tasks = [
Task.resolve(1),
Task.resolve(2),
Task.resolve(3),
];

const allResults = Task.all(tasks);
await Task.run(allResults); // Result.Ok([1, 2, 3])

// If any task fails, the whole operation fails
const withFailure = Task.all([
Task.resolve(1),
Task.reject('error'),
]);
await Task.run(withFailure); // Result.Error('error')

Task.any

Get the first successful result:

import { Task } from '@w5s/task';

const first = Task.any([
Task.reject('error 1'),
Task.resolve('success'),
Task.reject('error 2'),
]);
await Task.run(first); // Result.Ok('success')

Task.allSettled

Run all tasks and collect all results (success or failure):

import { Task } from '@w5s/task';

const settled = Task.allSettled([
Task.resolve(1),
Task.reject('error'),
Task.resolve(3),
]);
await Task.run(settled);
// Result.Ok([
// Result.Ok(1),
// Result.Error('error'),
// Result.Ok(3)
// ])

Utility Functions

Task.ignore

Discard the result value:

import { Task } from '@w5s/task';

const task = Task.resolve(42);
const ignored = Task.ignore(task);
await Task.run(ignored); // Result.Ok(undefined)