The following functions are used to create Tasks. They can be used from either the Task
class (for example Task.of
) or imported directly from the library as a function (simply, of
).
Many of these methods also exist on the Task instance. They exist here in their pure form in the hopes that the proposed Pipeline operator ever makes its way into the language.
Creates a task that will always succeed with the given value. Also aliased as succeed
. Similar in use to Promise.resolve
.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, number> = Task.of(5)
{% endtab %}
{% tab title="Type Definition" %}
type of = <S, E = any>(result: S) => Task<E, S>
{% endtab %} {% endtabs %}
Creates a task that will always succeed a result of void (undefined). Useful for chaining tasks which create side-effects, but don't return a useful value.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, void> = Task.empty()
{% endtab %}
{% tab title="Type Definition" %}
type empty = <E = any>() => Task<E, void>
{% endtab %} {% endtabs %}
Creates a task that will always succeed with the given value, but will take some number of milliseconds before it does. Useful for simulating latency or inserting timeouts in a task chain.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, number> = Task.succeedIn(100, 5)
{% endtab %}
{% tab title="Type Definition" %}
type succeedIn = <S, E = any>(ms: number, result: S) => Task<E, S>
{% endtab %} {% endtabs %}
Creates a task that will always succeed with the result of a function. Useful for bringing global state or side-effects into a task chain.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, number> = Task.succeedBy(() => 5)
{% endtab %}
{% tab title="Type Definition" %}
type succeedBy = <S, E = any>(result: () => S) => Task<E, S>
{% endtab %} {% endtabs %}
Creates a task that will always fail with the given error. Similar in use to Promise.reject
.
{% tabs %} {% tab title="Usage" %}
const task: Task<string, never> = Task.fail("error")
{% endtab %}
{% tab title="Type Definition" %}
type fail = <E, S = any>(error: E) => Task<E, S>
{% endtab %} {% endtabs %}
Creates a task that will always fail with the given error, but will take some number of milliseconds before it does. Useful for simulating latency or inserting timeouts in a task chain.
{% tabs %} {% tab title="Usage" %}
const task: Task<string, never> = Task.failIn(100, "error")
{% endtab %}
{% tab title="Type Definition" %}
type failIn = <E, S = any>(ms: number, error: E) => Task<E, S>
{% endtab %} {% endtabs %}
Creates a task that will always run an array of tasks in parallel. The result in a new task which returns the successful results as an array, in the same order as the tasks were given. If any task fails, the resulting task will fail with that error.
All task error and value types must be the same. If you need different types for the items of the array, consider using ap
instead.
Works similarly to Promise.all
.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, number[]> = Task.all([of(5), of(10)])
{% endtab %}
{% tab title="Type Definition" %}
type all<E, S> = (
tasksOrPromises: Array<Task<E, S> | Promise<S>>,
) => Task<E, S[]>
{% endtab %} {% endtabs %}
Creates a task that will always run an array of tasks in parallel. The result in a new task which returns the successful results as an array, in the same order as the tasks were given. Failed tasks will be excluded.
This can never fail, only return an empty array. Unliked Task.all
, the resulting array is in order of success, not initial input order.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, number[]> = Task.allSuccesses([fail("Err"), of(10)])
{% endtab %}
{% tab title="Type Definition" %}
type allSuccesses = <E, S>(
tasks: Array<Task<E, S>>,
): Task<never, S[]>
{% endtab %} {% endtabs %}
Creates a task that will always run an array of tasks serially. The result in a new task which returns the successful results as an array, in the same order as the tasks were given. If any task fails, the resulting task will fail with that error.
All task error and value types must be the same. If you need different types for the items of the array, consider simply chaining the tasks together in order.
Additionally, a second parameter can be added to allow concurrency. The default concurreny is 1
task at a time, but may be increased. Setting this concurrency to Infinity is the same a Task.all
.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, number[]> = Task.sequence([of(5), of(10)])
{% endtab %}
{% tab title="Type Definition" %}
type sequence = <E, S>(
tasksOrPromises: Array<Task<E, S> | Promise<S>>,
maxConcurrent = 1,
) => Task<E, S[]>
{% endtab %} {% endtabs %}
Creates a task that will always run an array of tasks in parallel and return the value of the first successful task. If all tasks fail, the error result will be an array of the error results from each task in the same order as they were given.
{% tabs %} {% tab title="Usage" %}
const task: Task<string[], number> = Task.firstSuccess([fail("error"), of(10)])
{% endtab %}
{% tab title="Type Definition" %}
type firstSuccess<E, S>(
tasksOrPromises: Array<Task<E, S> | Promise<S>>
) => Task<E[], S>;
{% endtab %} {% endtabs %}
Creates a task that will never succeed or fail. Uses the TypeScript never
type which allows the type checker to detect impossible cases and warn the developer at compile time.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, never> = Task.never()
{% endtab %}
{% tab title="Type Definition" %}
type never = () => Task<never, never>
{% endtab %} {% endtabs %}
Converts a Promise into a Task. Because Promise start in a "pending" state, that process will already be running before the Task is forked. This means that if two tasks chain off this one, the initial Promise (say, a web request) will only happen once. Consider using fromLazyPromise
instead to bring Promise more in line with the lazy Task philosophy.
Promise's do not track an error type (one of the reasons Tasks are more powerful) so the resulting Task is unable to infer the error type as well. It is recommended to pass it in as a generic.
{% tabs %} {% tab title="Usage" %}
const task: Task<unknown, Response> = Task.fromPromise(fetch(URL))
{% endtab %}
{% tab title="Type Definition" %}
type fromPromise = <S>(maybePromise: S | Promise<S>) => Task<unknown, S>
{% endtab %} {% endtabs %}
Converts an array of Promises into an array of Tasks and joins them with Task.all
Promise's do not track an error type (one of the reasons Tasks are more powerful) so the resulting Task is unable to infer the error type as well. It is recommended to pass it in as a generic.
{% tabs %} {% tab title="Usage" %}
const task: Task<unknown, Response> = Task.fromPromises(fetch(URL))
{% endtab %}
{% tab title="Type Definition" %}
type fromPromises = <S>(promises: Array<Promise<S>>) => Task<unknown, S[]>
{% endtab %} {% endtabs %}
Given a function which returns a Promise, turn that into a Task. This allows the Promise not to start until the Task forks (following the lazy philosophy of the rest of the library). This also means if two tasks chain from this one, the promise creating function will be called twice. See onlyOnce
if you wish to avoid this.
Promise's do not track an error type (one of the reasons Tasks are more powerful) so the resulting Task is unable to infer the error type as well. It is recommended to pass it in as a generic.
{% tabs %} {% tab title="Usage" %}
const task: Task<unknown, Response> = Task.fromLazyPromise(() => fetch(URL))
{% endtab %}
{% tab title="Type Definition" %}
type fromLazyPromise = <S, E = any>(
getPromise: () => S | Promise<S>,
) => Task<E, S>
{% endtab %} {% endtabs %}
Given a function which returns a Promise, create a new function which given the same arguments will now return a Task instead.
{% tabs %} {% tab title="Usage" %}
const taskFetch = wrapPromiseCreator(fetch)
const task: Task<unknown, Response> = taskFetch(URL)
{% endtab %}
{% tab title="Type Definition" %}
type wrapPromiseCreator = <S, Args extends unknown[]>(
fn: (...args: Args) => Promise<S>,
) => (...args: Args): Task<unknown, S>
{% endtab %} {% endtabs %}
Creates a task that will always run an array of tasks in parallel. The first task to finish is the resulting error or value. Useful for implementing network request timeouts by racing a task which fails in x milliseconds and a task which makes the request.
Works similarly to Promise.race
.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, number> = Task.race([
succeedIn(100, 5),
succeedIn(10, -5),
])
{% endtab %}
{% tab title="Type Definition" %}
type race = <E, S>(
tasksOrPromises: Array<Task<E, S> | Promise<S>>,
) => Task<E, S>
{% endtab %} {% endtabs %}
Creates a Task which can be controlled externally by providing reject
and resolve
methods. Must provide error and value types as generics.
Useful for integrating with callback based libraries and APIs.
{% tabs %} {% tab title="Usage" %}
const task = Task.external<never, number>()
try {
attemptSomething()
task.resolve(5)
} catch (e) {
task.reject(e)
}
{% endtab %}
{% tab title="Type Definition" %}
type external = <E, S>() => ExternalTask<E, S>
{% endtab %} {% endtabs %}
Creates a Task that wraps a callback. Also returns an emit
callback which when called, executes the callback. If the callback throws an exception, that is the error response of the task. Otherwise the returned value of the callback is considered a success.
Must provide error and value types as generics.
Useful for integrating with callback based libraries and APIs. Provides automatic exception handling.
{% tabs %} {% tab title="Usage" %}
const [task, emit] = Task.emitter<never, string>((e: Event) => {
attemptSomething()
return e.type
})
window.onclick = e => {
emit(e)
}
{% endtab %}
{% tab title="Type Definition" %}
type emitter = <Args extends any[], R>(
fn: (...args: Args) => R,
) => [ExternalTask<any, R>, (...args: Args) => void]
{% endtab %} {% endtabs %}
Given two tasks, run the successful values through a mapping function to combine them into a new output. Unlike Task.all
, this allows each task to be of a different type.
The function must be curried. That is, each parameter is handled one at a time. A version of this function which is not curried is available as zipWith
.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, [number, string]> = Task.map2(
a => b => [a, b],
Task.of(5),
Task.of("Hello"),
)
{% endtab %}
{% tab title="Type Definition" %}
type map2 = <E, E2, S, S2, S3>(
fn: (a: S) => (b: S2) => S3,
taskA: Task<E, S> | Promise<S>,
taskB: Task<E2, S2> | Promise<S2>,
) => Task<E | E2, S3>
{% endtab %} {% endtabs %}
Given three tasks, run the successful values through a mapping function to combine them into a new output. Unlike Task.all
, this allows each task to be of a different type.
The function must be curried. That is, each parameter is handled one at a time.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, [number, string, boolean]> = Task.map3(
a => b => c => [a, b, c],
Task.of(5),
Task.of("Hello"),
Task.of(true),
)
{% endtab %}
{% tab title="Type Definition" %}
type map3 = <E, E2, E3, S, S2, S3, S4>(
fn: (a: S) => (b: S2) => (c: S3) => S4,
taskA: Task<E, S> | Promise<S>,
taskB: Task<E2, S2> | Promise<S2>,
taskC: Task<E3, S3> | Promise<S3>,
) => Task<E | E2 | E3, S4>
{% endtab %} {% endtabs %}
Given four tasks, run the successful values through a mapping function to combine them into a new output. Unlike Task.all
, this allows each task to be of a different type.
The function must be curried. That is, each parameter is handled one at a time.
If you need to operate on more than 4 tasks, consider using ap
which can combine an arbitrary number of tasks using a mapping function.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, [number, string, boolean, Set<string>]> = Task.map4(
a => b => c => d => [a, b, c, d],
Task.of(5),
Task.of("Hello"),
Task.of(true),
Task.of(new Set(["hi"])),
)
{% endtab %}
{% tab title="Type Definition" %}
type map4 = <E, E2, E3, E4, S, S2, S3, S4, S5>(
fn: (a: S) => (b: S2) => (c: S3) => (d: S4) => S5,
taskA: Task<E, S> | Promise<S>,
taskB: Task<E2, S2> | Promise<S2>,
taskC: Task<E3, S3> | Promise<S3>,
taskD: Task<E4, S4> | Promise<S4>,
) => Task<E | E2 | E3 | E4, S5>
{% endtab %} {% endtabs %}
Allows the construction of a recursive, asynchrous loop. Given an initial starting value, call the currrent loop function and return a Task that contains either a LoopBreak
or LoopContinue
instance. The instance holds on to the current value. Will loop until it encounters a LoopBreak
.
This is a simplified version of what some will accomplish with a Array.prototype.reduce
which uses the current Promise as it's value.
{% tabs %} {% tab title="Usage" %}
// Count to six but wait 100ms between each step.
const task: Task<never, number> = Task.loop(num => {
if (num > 5) {
return Task.wait(100).forward(new LoopBreak(num))
}
return Task.wait(100).forward(new LoopContinue(num + 1))
}, 1)
{% endtab %}
{% tab title="Type Definition" %}
type loop = <E, S, T>(
fn: (currentValue: T) => Task<E, LoopBreak<S> | LoopContinue<T>>,
initialValue: T,
) => Task<E, S>
{% endtab %} {% endtabs %}
Works exactly like Array.prototype.reduce
, but asynchronously. The return value of each reducer must return a Task.
{% tabs %} {% tab title="Usage" %}
// Count to six but wait 100ms between each step.
const task: Task<never, number> = Task.reduce(
sum => Task.succeedIn(100, sum + 1),
0,
[1, 2, 3, 4, 5, 6],
)
{% endtab %}
{% tab title="Type Definition" %}
type reduce = <E, T, V>(
fn: (acc: V, currentValue: T, index: number, original: T[]) => Task<E, V>,
initialValue: V,
items: T[],
) => Task<E, V>
{% endtab %} {% endtabs %}
Given two tasks, return a new Task which succeeds with a 2-tuple of their successful results.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, [number, string]> = Task.zip(
Task.of(5),
Task.of("Hello"),
)
{% endtab %}
{% tab title="Type Definition" %}
type zip = <E, E2, S, S2>(
taskAOrPromise: Task<E, S> | Promise<S>,
taskBOrPromise: Task<E2, S2> | Promise<S2>,
) => Task<E | E2, [S, S2]>
{% endtab %} {% endtabs %}
Given two tasks, return a new Task which succeeds by running the successful results through a mapping function. Very similar to map2
, but zipWith
uses 1 parameter per successful Task. map2
uses a curried mapping function.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, [number, string]> = Task.zip(
Task.of(5),
Task.of("Hello"),
)
{% endtab %}
{% tab title="Type Definition" %}
type zip = <E, E2, S, S2>(
taskAOrPromise: Task<E, S> | Promise<S>,
taskBOrPromise: Task<E2, S2> | Promise<S2>,
) => Task<E | E2, [S, S2]>
{% endtab %} {% endtabs %}
Given a task which succeeds with another task, flatten into a single task which succeeds with the result of that nested task. Often this can be avoided by using chain
.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, number> = Task.flatten(Task.of(Task.of(5)))
{% endtab %}
{% tab title="Type Definition" %}
type flatten = <E, S>(task: Task<E, Task<E, S>>) => Task<E, S>
{% endtab %} {% endtabs %}
The applicative. If you know what that means, you'll be excited. If not, it is fine. This is a low level tool that helps build more complex features.
ap
starts with a Task which succeeds containing a function. Subsequence compositions of ap
will each provide a task. The success of all those tasks will be given to the initial task which resulted in a function. This is a type safe way of running map
on an arbitrary number of tasks. The function specifies its arguments, which must equal the number of ap
chains. Similar to Task.all
, but with 1 parameter per task, rather than an array, and it works with different task success types, rather than requiring all tasks succeed with the same type.
Also allows the definition of the mapping function to be asychronous because it is also inside a Task.
Without easy function composition in Javascript, for readability we recommend using the instance version .ap
rather than the nested calls the pure function requires.
{% tabs %} {% tab title="Usage" %}
const task: Task<never, number> = Task.ap(
Task.ap(
Task.ap(
Task.of((a, b, c) => a + b + c),
succeed(10) /* a */,
),
succeed(50) /* b */,
),
succeed(100) /* c */,
)
{% endtab %}
{% tab title="Type Definition" %}
type ap = <E, S, S2>(
taskOrPromise: Task<E, (result: S) => S2> | Promise<(result: S) => S2>,
appliedTaskOrPromise: Task<E, S> | Promise<S>,
) => Task<E, S2>
{% endtab %} {% endtabs %}