true-myth
- Version 9.0.0
- Published
- 636 kB
- No dependencies
- MIT license
Install
npm i true-myth
yarn add true-myth
pnpm add true-myth
Overview
Index
Variables
Interfaces
Type Aliases
Namespaces
maybe
- and()
- andThen()
- AnyArray
- AnyMaybe
- ap()
- equals()
- find()
- first()
- get()
- isInstance()
- isJust()
- IsMaybe
- isNothing()
- just
- Just
- JustJSON
- last()
- map()
- mapOr()
- mapOrElse()
- match()
- Matcher
- Maybe
- Maybe
- MaybeConstructor
- MaybeJSON
- NarrowingPredicate
- nothing
- Nothing
- NothingJSON
- of
- or()
- orElse()
- Predicate
- property()
- ReadonlyArray
- safe()
- T
- toJSON()
- toString()
- transposeArray()
- TransposedArray
- unwrapOr()
- unwrapOrElse()
- Unwrapped
- Variant
- Variant
task
- []
- []
- []
- A
- A
- A
- AggregateRejection
- all()
- allSettled()
- and()
- andThen()
- any()
- AnyTask
- AnyTask
- AnyTask
- fromPromise()
- fromResult()
- fromUnsafePromise()
- InvalidAccess
- isRetryFailed()
- IsTask
- map()
- mapRejected()
- match()
- Matcher
- or()
- orElse()
- Pending
- PhantomData
- race()
- readonly
- readonly
- readonly
- reject
- Rejected
- resolve
- Resolved
- RETRY_FAILED_NAME
- RetryFailed
- RetryStatus
- safe()
- safelyTry()
- safelyTryOr
- safelyTryOrElse
- safeNullable()
- State
- stopRetrying()
- StopRetrying
- Task
- Task
- TaskConstructor
- TaskExecutorException
- TaskTypesFor
- timeout()
- Timeout
- timer()
- Timer
- toPromise()
- tryOr()
- tryOrElse()
- UnsafePromise
- withResolvers
- WithResolvers
- withRetries()
Variables
variable Maybe
const Maybe: MaybeConstructor;
Maybe
represents a value which may () or may not () be present.
variable Result
const Result: ResultConstructor;
A
Result
represents success () or failure ().The behavior of this type is checked by TypeScript at compile time, and bears no runtime overhead other than the very small cost of the container object.
variable Task
const Task: TaskConstructor;
A
Task
is a type safe asynchronous computation.You can think of a
Task<T, E>
as being basically aPromise<Result<T, E>>
, because it *is* aPromise<Result<T, E>>
under the hood, but with two main differences from a “normal”Promise
:1. A
Task
*cannot* “reject”. All errors must be handled. This means that, like a , it will *never* throw an error if used in strict TypeScript.2. Unlike
Promise
,Task
robustly distinguishes betweenmap
andandThen
operations.Task
also implements JavaScript’sPromiseLike
interface, so you canawait
it; when aTask<T, E>
is awaited, it produces a .
variable Unit
const Unit: Unit;
The
Unit
type exists for the cases where you want a type-safe equivalent ofundefined
ornull
. It's a concrete instance, which won't blow up on you, and you can safely use it with e.g.Result
without being concerned that you'll accidentally introducenull
orundefined
back into your application.Equivalent to
()
or "unit" in many functional or functional-influenced languages.
Interfaces
interface Unit
interface Unit extends _Unit {}
Type Aliases
type Maybe
type Maybe<T> = Just<T> | Nothing<T>;
Maybe
represents a value which may () or may not () be present.
type Result
type Result<T, E> = Ok<T, E> | Err<T, E>;
A
Result
represents success () or failure ().The behavior of this type is checked by TypeScript at compile time, and bears no runtime overhead other than the very small cost of the container object.
type Task
type Task<T, E> = Pending<T, E> | Resolved<T, E> | Rejected<T, E>;
A
Task
is a type safe asynchronous computation.You can think of a
Task<T, E>
as being basically aPromise<Result<T, E>>
, because it *is* aPromise<Result<T, E>>
under the hood, but with two main differences from a “normal”Promise
:1. A
Task
*cannot* “reject”. All errors must be handled. This means that, like a , it will *never* throw an error if used in strict TypeScript.2. Unlike
Promise
,Task
robustly distinguishes betweenmap
andandThen
operations.Task
also implements JavaScript’sPromiseLike
interface, so you canawait
it; when aTask<T, E>
is awaited, it produces a .T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
Namespaces
namespace maybe
module 'dist/maybe.d.ts' {}
A value of type
T
which may, or may not, be present. If the value is present, it is . If it's absent, it is instead.For a deep dive on the type, see [the guide](/guide/understanding/maybe.md).
variable IsMaybe
const IsMaybe: Symbol;
variable just
const just: { <F extends (...args: any) => {}>(value: F): Maybe<F>; <T extends {}, F extends (...args: any) => T>(value: F): never; <F extends (...args: any) => null>(value: F): never; <T extends {}>(value: T): Maybe<T>;};
Create a instance which is a .
null
andundefined
are allowed by the type signature so that the function maythrow
on those rather than constructing a type likeMaybe<undefined>
.T The type of the item contained in the
Maybe
.Parameter value
The value to wrap in a
Maybe.Just
.Returns
An instance of
Maybe.Just<T>
.Throws
If you pass
null
orundefined
.
variable Maybe
const Maybe: MaybeConstructor;
Maybe
represents a value which may () or may not () be present.
variable nothing
const nothing: <T>(_?: null) => Nothing<T>;
Create a instance which is a .
If you want to create an instance with a specific type, e.g. for use in a function which expects a
Maybe<T>
where the<T>
is known but you have no value to give it, you can use a type parameter:```ts const notString = Maybe.nothing(); ```
T The type of the item contained in the
Maybe
.Returns
An instance of
Maybe.Nothing<T>
.
variable of
const of: { <F extends (...args: any) => {}>(value: F): Maybe<F>; <T extends {}, F extends (...args: any) => T>(value: F): never; <F extends (...args: any) => null>(value: F): never; <T extends {}>(value: T): Maybe<T>; <T>(value: T): Maybe<NonNullable<T>>;};
Create a from any value.
To specify that the result should be interpreted as a specific type, you may invoke
Maybe.of
with an explicit type parameter:```ts import * as maybe from 'true-myth/maybe'; const foo = maybe.of(null); ```
This is usually only important in two cases:
1. If you are intentionally constructing a
Nothing
from a knownnull
or undefined value *which is untyped*. 2. If you are specifying that the type is more general than the value passed (since TypeScript can define types as literals).T The type of the item contained in the
Maybe
.Parameter value
The value to wrap in a
Maybe
. If it isundefined
ornull
, the result will beNothing
; otherwise it will be the type of the value passed.
variable ReadonlyArray
const ReadonlyArray: any;
variable T
const T: any;
variable Variant
const Variant: { readonly Just: 'Just'; readonly Nothing: 'Nothing' };
Discriminant for the and type instances.
You can use the discriminant via the
variant
property of instances if you need to match explicitly on it.
function and
and: { <T, U>(andMaybe: Maybe<U>, maybe: Maybe<T>): Maybe<U>; <T, U>(andMaybe: Maybe<U>): (maybe: Maybe<T>) => Maybe<U>;};
You can think of this like a short-circuiting logical "and" operation on a type. If
maybe
is , then the result is theandMaybe
. Ifmaybe
is , the result isNothing
.This is useful when you have another
Maybe
value you want to provide if and only if* you have aJust
– that is, when you need to make sure that if youNothing
, whatever else you're handing aMaybe
to *also* gets aNothing
.Notice that, unlike in [
map
](#map) or its variants, the originalmaybe
is not involved in constructing the newMaybe
.#### Examples
```ts import Maybe from 'true-myth/maybe';
const justA = Maybe.just('A'); const justB = Maybe.just('B'); const nothing: Maybe = nothing();
console.log(Maybe.and(justB, justA).toString()); // Just(B) console.log(Maybe.and(justB, nothing).toString()); // Nothing console.log(Maybe.and(nothing, justA).toString()); // Nothing console.log(Maybe.and(nothing, nothing).toString()); // Nothing ```
T The type of the initial wrapped value. U The type of the wrapped value of the returned
Maybe
.Parameter andMaybe
The
Maybe
instance to return ifmaybe
isJust
Parameter maybe
The
Maybe
instance to check.Nothing
if the originalmaybe
isNothing
, orandMaybe
if the originalmaybe
isJust
.
function andThen
andThen: { <T, U>(thenFn: (t: T) => Maybe<U>, maybe: Maybe<T>): Maybe<U>; <T, R extends AnyMaybe>(thenFn: (t: T) => R, maybe: Maybe<T>): Maybe< ValueFor<R> >; <T, U>(thenFn: (t: T) => Maybe<U>): (maybe: Maybe<T>) => Maybe<U>; <T, R extends AnyMaybe>(thenFn: (t: T) => R): ( maybe: Maybe<T> ) => Maybe<ValueFor<R>>;};
Apply a function to the wrapped value if and return a new
Just
containing the resulting value; or return ifNothing
.This differs from in that
thenFn
returns anotherMaybe
. You can useandThen
to combine two functions which *both* create aMaybe
from an unwrapped type.You may find the
.then
method on an ES6Promise
helpful for comparison: if you have aPromise
, you can pass itsthen
method a callback which returns anotherPromise
, and the result will not be a *nested* promise, but a singlePromise
. The difference is thatPromise#then
unwraps *all* layers to only ever return a singlePromise
value, whereasMaybe.andThen
will not unwrap nestedMaybe
s.> [!NOTE] This is sometimes also known as
bind
, but *not* aliased as such > because [bind
already means something in JavaScript][bind].[bind]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
#### Example
(This is a somewhat contrived example, but it serves to show the way the function behaves.)
```ts import Maybe, { andThen, toString } from 'true-myth/maybe';
// string -> Maybe const toMaybeLength = (s: string) => Maybe.of(s.length);
// Maybe const aMaybeString = Maybe.of('Hello, there!');
// Maybe const resultingLength = andThen(toMaybeLength, aMaybeString); console.log(toString(resultingLength)); // 13 ```
Note that the result is not
Just(Just(13))
, butJust(13)
!T The type of the wrapped value. U The type of the wrapped value in the resulting
Maybe
.Parameter thenFn
The function to apply to the wrapped
T
ifmaybe
isJust
.Parameter maybe
The
Maybe
to evaluate and possibly apply a function to the contents of.Returns
The result of the
thenFn
(a newMaybe
) ifmaybe
is aJust
, otherwiseNothing
ifmaybe
is aNothing
.
function ap
ap: { <T, U extends {}>(maybeFn: Maybe<(t: T) => U>, maybe: Maybe<T>): Maybe<T | U>; <T, U extends {}>(maybeFn: Maybe<(t: T) => U>): ( maybe: Maybe<T> ) => Maybe<T | U>;};
Allows you to *apply* (thus
ap
) a value to a function without having to take either out of the context of their s. This does mean that the transforming function is itself within aMaybe
, which can be hard to grok at first but lets you do some very elegant things. For example,ap
allows you to this:```ts import { just, nothing } from 'true-myth/maybe';
const one = just(1); const five = just(5); const none = nothing();
const add = (a: number) => (b: number) => a + b; const maybeAdd = just(add);
maybeAdd.ap(one).ap(five); // Just(6) maybeAdd.ap(one).ap(none); // Nothing maybeAdd.ap(none).ap(five) // Nothing ```
Without
ap
, you'd need to do something like a nestedmatch
:```ts import { just, nothing } from 'true-myth/maybe';
const one = just(1); const five = just(5); const none = nothing();
one.match({ Just: n => five.match({ Just: o => just(n + o), Nothing: () => nothing(), }), Nothing: () => nothing(), }); // Just(6)
one.match({ Just: n => none.match({ Just: o => just(n + o), Nothing: () => nothing(), }), Nothing: () => nothing(), }); // Nothing
none.match({ Just: n => five.match({ Just: o => just(n + o), Nothing: () => nothing(), }), Nothing: () => nothing(), }); // Nothing ```
And this kind of thing comes up quite often once you're using
Maybe
to handle optionality throughout your application.For another example, imagine you need to compare the equality of two ImmutableJS data structures, where a
===
comparison won't work. Withap
, that's as simple as this:```ts import Maybe from 'true-myth/maybe'; import { is as immutableIs, Set } from 'immutable';
const is = (first: unknown) => (second: unknown) => immutableIs(first, second);
const x = Maybe.of(Set.of(1, 2, 3)); const y = Maybe.of(Set.of(2, 3, 4));
Maybe.of(is).ap(x).ap(y); // Just(false) ```
Without
ap
, we're back to that gnarly nestedmatch
:```ts import Maybe, { just, nothing } from 'true-myth/maybe'; import { is, Set } from 'immutable';
const x = Maybe.of(Set.of(1, 2, 3)); const y = Maybe.of(Set.of(2, 3, 4));
x.match({ Just: iX => y.match({ Just: iY => Maybe.just(is(iX, iY)), Nothing: () => Maybe.nothing(), }) Nothing: () => Maybe.nothing(), }); // Just(false) ```
In summary: anywhere you have two
Maybe
instances and need to perform an operation that uses both of them,ap
is your friend.Two things to note, both regarding *currying*:
1. All functions passed to
ap
must be curried. That is, they must be of the form (for add)(a: number) => (b: number) => a + b
, *not* the more usual(a: number, b: number) => a + b
you see in JavaScript more generally.(Unfortunately, these do not currently work with lodash or Ramda's
curry
helper functions. A future update to the type definitions may make that work, but the intermediate types produced by those helpers and the more general function types expected by this function do not currently align.)2. You will need to call
ap
as many times as there are arguments to the function you're dealing with. So in the case of thisadd3
function, which has the "arity" (function argument count) of 3 (a
andb
), you'll need to callap
twice: once fora
, and once forb
. To see why, let's look at what the result in each phase is:```ts const add3 = (a: number) => (b: number) => (c: number) => a + b + c;
const maybeAdd = just(add3); // Just((a: number) => (b: number) => (c: number) => a + b + c) const maybeAdd1 = maybeAdd.ap(just(1)); // Just((b: number) => (c: number) => 1 + b + c) const maybeAdd1And2 = maybeAdd1.ap(just(2)) // Just((c: number) => 1 + 2 + c) const final = maybeAdd1.ap(just(3)); // Just(4) ```
So for
toString
, which just takes a single argument, you would only need to callap
once.```ts const toStr = (v: { toString(): string }) => v.toString(); just(toStr).ap(12); // Just("12") ```
One other scenario which doesn't come up *quite* as often but is conceivable is where you have something that may or may not actually construct a function for handling a specific
Maybe
scenario. In that case, you can wrap the possibly-present inap
and then wrap the values to apply to the function to inMaybe
themselves.__Aside:__
ap
is not namedapply
because of the overlap with JavaScript's existing [apply
] function – and although strictly speaking, there isn't any direct overlap (Maybe.apply
andFunction.prototype.apply
don't intersect at all) it's useful to have a different name to avoid implying that they're the same.[
apply
]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/applyParameter maybeFn
maybe a function from T to U
Parameter maybe
maybe a T to apply to
fn
function equals
equals: { <T>(mb: Maybe<T>, ma: Maybe<T>): boolean; <T>(mb: Maybe<T>): (ma: Maybe<T>) => boolean;};
Allows quick triple-equal equality check between the values inside two instances without having to unwrap them first.
```ts const a = Maybe.of(3); const b = Maybe.of(3); const c = Maybe.of(null); const d = Maybe.nothing();
Maybe.equals(a, b); // true Maybe.equals(a, c); // false Maybe.equals(c, d); // true ```
Parameter mb
A
maybe
to compare to.Parameter ma
A
maybe
instance to check.
function find
find: { <T, U extends T>(predicate: NarrowingPredicate<T, U>, array: AnyArray<T>): Maybe< NonNullable<U> >; <T, U extends T>(predicate: NarrowingPredicate<T, U>): ( array: AnyArray<T> ) => Maybe<NonNullable<U>>; <T>(predicate: Predicate<T>, array: AnyArray<T>): Maybe<NonNullable<T>>; <T>(predicate: Predicate<T>): (array: AnyArray<T>) => Maybe<NonNullable<T>>;};
Safely search for an element in an array.
This function behaves like
Array.prototype.find
, but returnsMaybe<T>
instead ofT | undefined
.## Examples
The basic form is:
```ts import Maybe from 'true-myth/maybe';
let array = [1, 2, 3]; Maybe.find(v => v > 1, array); // Just(2) Maybe.find(v => v < 1, array); // Nothing ```
The function is curried so you can use it in a functional chain. For example (leaving aside error handling on a bad response for simplicity), suppose the url
https://arrays.example.com
returned a JSON payload with the typeArray<{ count: number, name: string }>
, and we wanted to get the first of these wherecount
was at least 100. We could write this:```ts import Maybe from 'true-myth/maybe';
type Item = { count: number; name: string }; type Response = Array;
// curried variant! const findAtLeast100 = Maybe.find(({ count }: Item) => count > 100);
fetch('https://arrays.example.com') .then(response => response.json() as Response) .then(findAtLeast100) .then(found => { if (found.isJust) { console.log(
The matching value is ${found.value.name}!
); } }); ```Parameter predicate
A function to execute on each value in the array, returning
true
when the item in the array matches the condition. The signature forpredicate
is identical to the signature for the first argument toArray.prototype.find
. The function is called once for each element of the array, in ascending order, until it finds one where predicate returns true. If such an element is found, find immediately returns that element value wrapped inJust
. Otherwise,Maybe.find
returnsNothing
.Parameter array
The array to search using the predicate.
function first
first: { <T extends {}>(array: AnyArray<T>): Maybe<Just<T>>; (array: AnyArray<null>): Maybe<Nothing<{}>>; <T extends {}>(array: AnyArray<T>): Maybe<Maybe<T>>;};
Safely get the first item from a list, returning the first item if the array has at least one item in it, or if it is empty.
This produces a
Maybe<Maybe<T>>
rather thanMaybe<T>
to distinguish between arrays that includenull
orundefined
and empty arrays.## Examples
```ts import { first } from 'true-myth/maybe';
let empty = []; first(empty); // => Nothing
let full = [1, 2, 3]; first(full); // => Just(Just(1))
let mixed = [undefined, 1]; first(mixed); // => Just(Nothing) ```
Unfortunately, it is not possible to distinguish between these statically in a single call signature in a reasonable way: we do not want to change the types and runtime result produced by calling this function simply because the input array had *its* type changed (to include, or not include,
null
orundefined
). Although the types and runtime could be guaranteed to align, correctly doing so would require every item in the array to check whether *any* items arenull
orundefined
, making the performance linear on the size of the array ($O(n)$) instead of constant time ($O(1)$). This is _quite_ undesirable!Parameter array
The array to get the first item from.
function get
get: { <T extends { [key: string]: unknown }, K extends keyof T>( key: K, maybeObj: Maybe<T> ): Maybe<NonNullable<T[K]>>; <T extends { [key: string]: unknown }, K extends keyof T>(key: K): ( maybeObj: Maybe<T> ) => Maybe<NonNullable<T[K]>>;};
Safely extract a key from a of an object, returning if the key has a value on the object and if it does not. (Like but operating on a
Maybe<T>
rather than directly on aT
.)The check is type-safe: you won't even be able to compile if you try to look up a property that TypeScript *knows* doesn't exist on the object.
```ts import { get, just, nothing } from 'true-myth/maybe';
type Person = { name?: string };
const me: Maybe = just({ name: 'Chris' }); console.log(get('name', me)); // Just('Chris')
const nobody = nothing(); console.log(get('name', nobody)); // Nothing ```
However, it also works correctly with dictionary types:
```ts import { get, just } from 'true-myth/maybe';
type Dict = { [key: string]: T };
const score: Maybe<Dict> = just({ player1: 0, player2: 1 });
console.log(get('player1', score)); // Just(0) console.log(get('player2', score)); // Just(1) console.log(get('player3', score)); // Nothing ```
The order of keys is so that it can be partially applied:
```ts import { get, just } from 'true-myth/maybe';
type Person = { name?: string };
const lookupName = get('name');
const me: Person = { name: 'Chris' }; console.log(lookupName(me)); // Just('Chris')
const nobody: Person = {}; console.log(lookupName(nobody)); // Nothing ```
Parameter key
The key to pull out of the object.
Parameter maybeObj
The object to look up the key from.
function isInstance
isInstance: <T>(item: unknown) => item is Maybe<T>;
Determine whether an item is an instance of .
Parameter item
The item to check.
function isJust
isJust: <T>(maybe: Maybe<T>) => maybe is Just<T>;
Is the a ?
T The type of the item contained in the
Maybe
.Parameter maybe
The
Maybe
to check.Returns
A type guarded
Just
.
function isNothing
isNothing: <T>(maybe: Maybe<T>) => maybe is Nothing<T>;
Is the a ?
T The type of the item contained in the
Maybe
.Parameter maybe
The
Maybe
to check.Returns
A type guarded
Nothing
.
function last
last: { <T extends {}>(array: AnyArray<T>): Maybe<Just<T>>; (array: AnyArray<null>): Maybe<Nothing<never>>; <T extends {}>(array: AnyArray<T>): Maybe<Maybe<T>>;};
Safely get the last item from a list, returning the last item if the array has at least one item in it, or if it is empty.
This produces a
Maybe<Maybe<T>>
rather thanMaybe<T>
to distinguish between arrays that includenull
orundefined
and empty arrays.## Examples
```ts import { last } from 'true-myth/maybe';
let empty = []; last(empty); // => Nothing
let full = [1, 2, 3]; last(full); // => Just(Just(3))
let mixed = [1, null, 2, null]; last(mixed); // Just(Nothing) ```
Unfortunately, it is not possible to distinguish between these statically in a single call signature in a reasonable way: we do not want to change the types and runtime result produced by calling this function simply because the input array had *its* type changed (to include, or not include,
null
orundefined
). Although the types and runtime could be guaranteed to align, correctly doing so would require every item in the array to check whether *any* items arenull
orundefined
, making the performance linear on the size of the array ($O(n)$) instead of constant time ($O(1)$). This is _quite_ undesirable!Parameter array
The array to get the first item from.
function map
map: { <T, U extends {}>(mapFn: (t: T) => U): (maybe: Maybe<T>) => Maybe<U>; <T, U extends {}>(mapFn: (t: T) => U, maybe: Maybe<T>): Maybe<U>;};
Map over a instance: apply the function to the wrapped value if the instance is , and return if the instance is
Nothing
.map
works a lot likeArray.prototype.map
:Maybe
andArray
are both containers* for other things. If you have no items in an array of numbers namedfoo
and callfoo.map(x => x + 1)
, you'll still just have an array with nothing in it. But if you have any items in the array ([2, 3]
), and you callfoo.map(x => x + 1)
on it, you'll get a new array with each of those items inside the array "container" transformed ([3, 4]
).That's exactly what's happening with
map
. If the container is *empty* – theNothing
variant – you just get back an empty container. If the container has something in it – theJust
variant – you get back a container with the item inside transformed.(So... why not just use an array? The biggest reason is that an array can be any length. With a
Maybe
, we're capturing the idea of "something or nothing" rather than "0 to n" items. And this lets us implement a whole set of *other* interfaces, like those in this module.)#### Examples
```ts const length = (s: string) => s.length;
const justAString = Maybe.just('string'); const justTheStringLength = map(length, justAString); console.log(justTheStringLength.toString()); // Just(6)
const notAString = Maybe.nothing(); const notAStringLength = map(length, notAString); console.log(notAStringLength.toString()); // "Nothing" ```
T The type of the wrapped value. U The type of the wrapped value of the returned
Maybe
.Parameter mapFn
The function to apply the value to if
Maybe
isJust
.Returns
A function accepting a
Maybe<T>
, which will produceMaybe<U>
after applyingmapFn
.Map over a instance: apply the function to the wrapped value if the instance is , and return if the instance is
Nothing
.map
works a lot likeArray.prototype.map
:Maybe
andArray
are both containers* for other things. If you have no items in an array of numbers namedfoo
and callfoo.map(x => x + 1)
, you'll still just have an array with nothing in it. But if you have any items in the array ([2, 3]
), and you callfoo.map(x => x + 1)
on it, you'll get a new array with each of those items inside the array "container" transformed ([3, 4]
).That's exactly what's happening with
map
. If the container is *empty* – theNothing
variant – you just get back an empty container. If the container has something in it – theJust
variant – you get back a container with the item inside transformed.(So... why not just use an array? The biggest reason is that an array can be any length. With a
Maybe
, we're capturing the idea of "something or nothing" rather than "0 to n" items. And this lets us implement a whole set of *other* interfaces, like those in this module.)#### Examples
```ts const length = (s: string) => s.length;
const justAString = Maybe.just('string'); const justTheStringLength = map(length, justAString); console.log(justTheStringLength.toString()); // Just(6)
const notAString = Maybe.nothing(); const notAStringLength = map(length, notAString); console.log(notAStringLength.toString()); // "Nothing" ```
T The type of the wrapped value. U The type of the wrapped value of the returned
Maybe
.Parameter mapFn
The function to apply the value to if
Maybe
isJust
.Parameter maybe
The
Maybe
instance to map over.Returns
A new
Maybe
with the result of applyingmapFn
to the value in aJust
, orNothing
ifmaybe
isNothing
.
function mapOr
mapOr: { <T, U>(orU: U, mapFn: (t: T) => U, maybe: Maybe<T>): U; <T, U>(orU: U, mapFn: (t: T) => U): (maybe: Maybe<T>) => U; <T, U>(orU: U): (mapFn: (t: T) => U) => (maybe: Maybe<T>) => U;};
Map over a instance and get out the value if
maybe
is a , or return a default value ifmaybe
is a .#### Examples
```ts const length = (s: string) => s.length;
const justAString = Maybe.just('string'); const theStringLength = mapOr(0, length, justAString); console.log(theStringLength); // 6
const notAString = Maybe.nothing(); const notAStringLength = mapOr(0, length, notAString) console.log(notAStringLength); // 0 ```
T The type of the wrapped value. U The type of the wrapped value of the returned
Maybe
.Parameter orU
The default value to use if
maybe
isNothing
Parameter mapFn
The function to apply the value to if
Maybe
isJust
Parameter maybe
The
Maybe
instance to map over.
function mapOrElse
mapOrElse: { <T, U>(orElseFn: () => U, mapFn: (t: T) => U, maybe: Maybe<T>): U; <T, U>(orElseFn: () => U, mapFn: (t: T) => U): (maybe: Maybe<T>) => U; <T, U>(orElseFn: () => U): (mapFn: (t: T) => U) => (maybe: Maybe<T>) => U;};
Map over a instance and get out the value if
maybe
is a , or use a function to construct a default value ifmaybe
is .#### Examples
```ts const length = (s: string) => s.length; const getDefault = () => 0;
const justAString = Maybe.just('string'); const theStringLength = mapOrElse(getDefault, length, justAString); console.log(theStringLength); // 6
const notAString = Maybe.nothing(); const notAStringLength = mapOrElse(getDefault, length, notAString) console.log(notAStringLength); // 0 ```
T The type of the wrapped value. U The type of the wrapped value of the returned
Maybe
.Parameter orElseFn
The function to apply if
maybe
isNothing
.Parameter mapFn
The function to apply to the wrapped value if
maybe
isJust
Parameter maybe
The
Maybe
instance to map over.
function match
match: { <T, A>(matcher: Matcher<T, A>, maybe: Maybe<T>): A; <T, A>(matcher: Matcher<T, A>): (m: Maybe<T>) => A;};
Performs the same basic functionality as , but instead of simply unwrapping the value if it is and applying a value to generate the same default type if it is , lets you supply functions which may transform the wrapped type if it is
Just
or get a default value forNothing
.This is kind of like a poor man's version of pattern matching, which JavaScript currently lacks.
Instead of code like this:
```ts import Maybe from 'true-myth/maybe';
const logValue = (mightBeANumber: Maybe) => { const valueToLog = Maybe.mightBeANumber.isJust ? mightBeANumber.value.toString() : 'Nothing to log.';
console.log(valueToLog); }; ```
...we can write code like this:
```ts import { match } from 'true-myth/maybe';
const logValue = (mightBeANumber: Maybe) => { const value = match( { Just: n => n.toString(), Nothing: () => 'Nothing to log.', }, mightBeANumber );
console.log(value); }; ```
This is slightly longer to write, but clearer: the more complex the resulting expression, the hairer it is to understand the ternary. Thus, this is especially convenient for times when there is a complex result, e.g. when rendering part of a React component inline in JSX/TSX.
Parameter matcher
A lightweight object defining what to do in the case of each variant.
Parameter maybe
The
maybe
instance to check.
function or
or: { <T>(defaultMaybe: Maybe<T>, maybe: Maybe<T>): Maybe<T>; <T>(defaultMaybe: Maybe<T>): (maybe: Maybe<T>) => Maybe<T>;};
Provide a fallback for a given . Behaves like a logical
or
: if themaybe
value is a , returns thatmaybe
; otherwise, returns thedefaultMaybe
value.This is useful when you want to make sure that something which takes a
Maybe
always ends up getting aJust
variant, by supplying a default value for the case that you currently have a nothing.```ts import Maybe from 'true-utils/maybe';
const justA = Maybe.just("a"); const justB = Maybe.just("b"); const aNothing: Maybe = nothing();
console.log(Maybe.or(justB, justA).toString()); // Just(A) console.log(Maybe.or(aNothing, justA).toString()); // Just(A) console.log(Maybe.or(justB, aNothing).toString()); // Just(B) console.log(Maybe.or(aNothing, aNothing).toString()); // Nothing ```
T The type of the wrapped value.
Parameter defaultMaybe
The
Maybe
to use ifmaybe
is aNothing
.Parameter maybe
The
Maybe
instance to evaluate.Returns
maybe
if it is aJust
, otherwisedefaultMaybe
.
function orElse
orElse: { <T, R extends AnyMaybe>(elseFn: () => R, maybe: Maybe<T>): Maybe<ValueFor<R>>; <T, R extends AnyMaybe>(elseFn: () => R): ( maybe: Maybe<T> ) => Maybe<ValueFor<R>>;};
Like , but using a function to construct the alternative .
Sometimes you need to perform an operation using other data in the environment to construct the fallback value. In these situations, you can pass a function (which may be a closure) as the
elseFn
to generate the fallbackMaybe<T>
.Useful for transforming empty scenarios based on values in context.
T The type of the wrapped value.
Parameter elseFn
The function to apply if
maybe
isNothing
Parameter maybe
The
maybe
to use if it isJust
.Returns
The
maybe
if it isJust
, or theMaybe
returned byelseFn
if themaybe
isNothing
.
function property
property: { <T, K extends keyof T>(key: K, obj: T): Maybe<NonNullable<T[K]>>; <T, K extends keyof T>(key: K): (obj: T) => Maybe<NonNullable<T[K]>>;};
Safely extract a key from an object, returning if the key has a value on the object and if it does not.
The check is type-safe: you won't even be able to compile if you try to look up a property that TypeScript *knows* doesn't exist on the object.
```ts type Person = { name?: string };
const me: Person = { name: 'Chris' }; console.log(Maybe.property('name', me)); // Just('Chris')
const nobody: Person = {}; console.log(Maybe.property('name', nobody)); // Nothing ```
However, it also works correctly with dictionary types:
```ts import * as maybe from 'true-myth/maybe';
type Dict = { [key: string]: T };
const score: Dict = { player1: 0, player2: 1 };
console.log(maybe.property('player1', score)); // Just(0) console.log(maybe.property('player2', score)); // Just(1) console.log(maybe.property('player3', score)); // Nothing ```
The order of keys is so that it can be partially applied:
```ts type Person = { name?: string };
const lookupName = maybe.property('name');
const me: Person = { name: 'Chris' }; console.log(lookupName(me)); // Just('Chris')
const nobody: Person = {}; console.log(lookupName(nobody)); // Nothing ```
Parameter key
The key to pull out of the object.
Parameter obj
The object to look up the key from.
function safe
safe: < F extends AnyFunction, P extends Parameters<F>, R extends NonNullable<ReturnType<F>>>( fn: F) => (...params: P) => Maybe<R>;
Transform a function from a normal JS function which may return
null
orundefined
to a function which returns a instead.For example, dealing with the
Document#querySelector
DOM API involves a lot* of things which can benull
:```ts const foo = document.querySelector('#foo'); let width: number; if (foo !== null) { width = foo.getBoundingClientRect().width; } else { width = 0; }
const getStyle = (el: HTMLElement, rule: string) => el.style[rule]; const bar = document.querySelector('.bar'); let color: string; if (bar != null) { let possibleColor = getStyle(bar, 'color'); if (possibleColor !== null) { color = possibleColor; } else { color = 'black'; } } ```
(Imagine in this example that there were more than two options: the simplifying workarounds you commonly use to make this terser in JS, like the ternary operator or the short-circuiting
||
or??
operators, eventually become very confusing with more complicated flows.)We can work around this with
Maybe
, always wrapping each layer in invocations, and this is *somewhat* better:```ts import Maybe from 'true-myth/maybe';
const aWidth = Maybe.of(document.querySelector('#foo')) .map(el => el.getBoundingClientRect().width) .unwrapOr(0);
const aColor = Maybe.of(document.querySelector('.bar')) .andThen(el => Maybe.of(getStyle(el, 'color')) .unwrapOr('black'); ```
With
safe
, though, you can create a transformed version of a function once* and then be able to use it freely throughout your codebase, *always* getting back aMaybe
:```ts import { safe } from 'true-myth/maybe';
const querySelector = safe(document.querySelector.bind(document)); const safelyGetStyle = safe(getStyle);
const aWidth = querySelector('#foo') .map(el => el.getBoundingClientRect().width) .unwrapOr(0);
const aColor = querySelector('.bar') .andThen(el => safelyGetStyle(el, 'color')) .unwrapOr('black'); ```
Parameter fn
The function to transform; the resulting function will have the exact same signature except for its return type.
function toJSON
toJSON: <T>(maybe: Maybe<T>) => MaybeJSON<unknown>;
Create an
Object
representation of a instance.Useful for serialization.
JSON.stringify()
uses it.Parameter maybe
The value to convert to JSON
Returns
The JSON representation of the
Maybe
function toString
toString: <T>(maybe: Maybe<T>) => string;
Create a
String
representation of a instance.A instance will be
Just(<representation of the value>)
, where the representation of the value is simply the value's owntoString
representation. For example:| call | output | |----------------------------------------|-------------------------| |
toString(Maybe.of(42))
|Just(42)
| |toString(Maybe.of([1, 2, 3]))
|Just(1,2,3)
| |toString(Maybe.of({ an: 'object' }))
|Just([object Object])
| |toString(Maybe.nothing())
|Nothing
|T The type of the wrapped value; its own
.toString
will be used to print the interior contents of theJust
variant.Parameter maybe
The value to convert to a string.
Returns
The string representation of the
Maybe
.
function transposeArray
transposeArray: () => any;
Given an array or tuple of s, return a
Maybe
of the array or tuple values.- Given an array of type
Array<Maybe<A> | Maybe<B>>
, the resulting type isMaybe<Array<A | B>>
. - Given a tuple of type[Maybe<A>, Maybe<B>]
, the resulting type isMaybe<[A, B]>
.If any of the items in the array or tuple are , the whole result is
Nothing
. If all items in the array or tuple are , the whole result isJust
.## Examples
Given an array with a mix of
Maybe
types in it, bothallJust
andmixed
here will have the typeMaybe<Array<string | number>>
, but will beJust
andNothing
respectively.```ts import Maybe, { transposeArray } from 'true-myth/maybe';
let valid = [Maybe.just(2), Maybe.just('three')]; let allJust = transposeArray(valid); // => Just([2, 'three']);
let invalid = [Maybe.just(2), Maybe.nothing()]; let mixed = transposeArray(invalid); // => Nothing ```
When working with a tuple type, the structure of the tuple is preserved. Here, for example,
result
has the typeMaybe<[string, number]>
and will beNothing
:```ts import Maybe, { transposeArray } from 'true-myth/maybe';
type Tuple = [Maybe, Maybe];
let invalid: Tuple = [Maybe.just('wat'), Maybe.nothing()]; let result = transposeArray(invalid); // => Nothing ```
If all of the items in the tuple are
Just
, the result isJust
wrapping the tuple of the values of the items. Here, for example,result
again has the typeMaybe<[string, number]>
and will beJust(['hey', 12]
:```ts import Maybe, { transposeArray } from 'true-myth/maybe';
type Tuple = [Maybe, Maybe];
let valid: Tuple = [Maybe.just('hey'), Maybe.just(12)]; let result = transposeArray(valid); // => Just(['hey', 12]) ```
Parameter maybes
The
Maybe
s to resolve to a singleMaybe
.
function unwrapOr
unwrapOr: { <T, U>(defaultValue: U, maybe: Maybe<T>): T | U; <T, U>(defaultValue: U): (maybe: Maybe<T>) => T | U;};
Safely get the value out of a .
Returns the content of a or
defaultValue
if . This is the recommended way to get a value out of aMaybe
most of the time.```ts import Maybe from 'true-myth/maybe';
const notAString = Maybe.nothing(); const isAString = Maybe.just('look ma! some characters!');
console.log(Maybe.unwrapOr('', notAString)); // "" console.log(Maybe.unwrapOr('', isAString)); // "look ma! some characters!" ```
T The type of the wrapped value.
Parameter defaultValue
The value to return if
maybe
is aNothing
.Parameter maybe
The
Maybe
instance to unwrap if it is aJust
.Returns
The content of
maybe
if it is aJust
, otherwisedefaultValue
.
function unwrapOrElse
unwrapOrElse: { <T, U>(orElseFn: () => U, maybe: Maybe<T>): T | U; <T, U>(orElseFn: () => U): (maybe: Maybe<T>) => T | U;};
Safely get the value out of a by returning the wrapped value if it is , or by applying
orElseFn
if it is .This is useful when you need to *generate* a value (e.g. by using current values in the environment – whether preloaded or by local closure) instead of having a single default value available (as in ).
```ts import Maybe from 'true-myth/maybe';
// You can imagine that someOtherValue might be dynamic. const someOtherValue = 99; const handleNothing = () => someOtherValue;
const aJust = Maybe.just(42); console.log(Maybe.unwrapOrElse(handleNothing, aJust)); // 42
const aNothing = nothing(); console.log(Maybe.unwrapOrElse(handleNothing, aNothing)); // 99 ```
T The wrapped value.
Parameter orElseFn
A function used to generate a valid value if
maybe
is aNothing
.Parameter maybe
The
Maybe
instance to unwrap if it is aJust
Returns
Either the content of
maybe
or the value returned fromorElseFn
.
interface Just
interface Just<T> extends MaybeImpl<T> {}
A
Just
instance is the *present* variant instance of the type, representing the presence of a value which may be absent. For a full discussion, see the module docs.T The type wrapped in this
Just
variant ofMaybe
.
interface MaybeConstructor
interface MaybeConstructor {}
The public interface for the class *as a value*: a constructor and the associated static properties.
property just
just: typeof MaybeImpl.just;
property nothing
nothing: typeof MaybeImpl.nothing;
property of
of: typeof MaybeImpl.of;
construct signature
new <T>(value?: T | null | undefined): Maybe<T>;
interface Nothing
interface Nothing<T> extends Omit<MaybeImpl<T>, 'value'> {}
A
Nothing
instance is the *absent* variant instance of the type, representing the presence of a value which may be absent. For a full discussion, see the module docs.T The type which would be wrapped in a variant of the .
interface NothingJSON
interface NothingJSON {}
property variant
variant: 'Nothing';
type AnyArray
type AnyArray<T> = Array<T> | ReadonlyArray<T>;
An array or a readonly array.
type AnyMaybe
type AnyMaybe = Maybe<{}>;
A convenient way to name
Maybe<{}>
.
type Matcher
type Matcher<T, A> = { Just: (value: T) => A; Nothing: () => A;};
A lightweight object defining how to handle each variant of a .
type Maybe
type Maybe<T> = Just<T> | Nothing<T>;
Maybe
represents a value which may () or may not () be present.
type MaybeJSON
type MaybeJSON<T> = JustJSON<T> | NothingJSON;
type NarrowingPredicate
type NarrowingPredicate<T, U extends T> = ( element: T, index: number, array: AnyArray<T>) => element is U;
type Predicate
type Predicate<T> = (element: T, index: number, array: AnyArray<T>) => boolean;
type TransposedArray
type TransposedArray<T extends ReadonlyArray<Maybe<unknown>>> = Array<unknown> extends T ? Maybe<{ -readonly [K in keyof T]: Unwrapped<T[K]>; }> : Maybe<{ [K in keyof T]: Unwrapped<T[K]>; }>;
type Unwrapped
type Unwrapped<T> = T extends Maybe<infer U> ? U : T;
type Variant
type Variant = keyof typeof Variant;
namespace result
module 'dist/result.d.ts' {}
A is a type representing the value result of a synchronous operation which may fail, with a successful value of type
T
or an error of typeE
.If the result is a success, it is . If the result is a failure, it is .
For a deep dive on the type, see [the guide](/guide/understanding/result.md).
variable err
const err: { <T = never, E = unknown>(): Result<T, Unit>; <T = never, E = unknown>(error: E): Result<T, E>;};
Create an instance of .
If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter:
```ts const notString = Result.err<number, string>('something went wrong'); ```
Note: passing nothing, or passing
null
orundefined
explicitly, will produce aResult<T, Unit>
, rather than producing the nonsensical and in practice quite annoyingResult<null, string>
etc. See for more.```ts const normalResult = Result.err<number, string>('oh no'); const explicitUnit = Result.err<number, Unit>(Unit); const implicitUnit = Result.err<number, Unit>(); ```
In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient.
```ts type SomeData = { //... };
const isValid = (data: SomeData): boolean => { // true or false... }
const arrowValidate = (data: SomeData): Result<number, Unit> => isValid(data) ? Result.ok(42) : Result.err();
function fnValidate(data: someData): Result<number, Unit> { return isValid(data) ? Result.ok(42) : Result.err(); } ```
T The type of the item contained in the
Result
.Parameter E
The error value to wrap in a
Result.Err
.
variable IsResult
const IsResult: Symbol;
variable ok
const ok: { (): Result<Unit, never>; <T, E = never>(value: T): Result<T, E> };
Create an instance of .
If you need to create an instance with a specific type (as you do whenever you are not constructing immediately for a function return or as an argument to a function), you can use a type parameter:
```ts const yayNumber = Result.ok<number, string>(12); ```
Note: passing nothing, or passing
null
orundefined
explicitly, will produce aResult<Unit, E>
, rather than producing the nonsensical and in practice quite annoyingResult<null, string>
etc. See for more.```ts const normalResult = Result.ok<number, string>(42); const explicitUnit = Result.ok<Unit, string>(Unit); const implicitUnit = Result.ok<Unit, string>(); ```
In the context of an immediate function return, or an arrow function with a single expression value, you do not have to specify the types, so this can be quite convenient.
```ts type SomeData = { //... };
const isValid = (data: SomeData): boolean => { // true or false... }
const arrowValidate = (data: SomeData): Result<Unit, string> => isValid(data) ? Result.ok() : Result.err('something was wrong!');
function fnValidate(data: someData): Result<Unit, string> { return isValid(data) ? Result.ok() : Result.err('something was wrong'); } ```
T The type of the item contained in the
Result
.Parameter value
The value to wrap in a
Result.Ok
.
variable Result
const Result: ResultConstructor;
A
Result
represents success () or failure ().The behavior of this type is checked by TypeScript at compile time, and bears no runtime overhead other than the very small cost of the container object.
variable Variant
const Variant: { readonly Ok: 'Ok'; readonly Err: 'Err' };
Discriminant for and variants of the type.
You can use the discriminant via the
variant
property ofResult
instances if you need to match explicitly on it.
function and
and: { <T, U, E>(andResult: Result<U, E>, result: Result<T, E>): Result<U, E>; <T, U, E>(andResult: Result<U, E>): (result: Result<T, E>) => Result<U, E>;};
You can think of this like a short-circuiting logical "and" operation on a type. If
result
is , then the result is theandResult
. Ifresult
is , the result is theErr
.This is useful when you have another
Result
value you want to provide if and only if* you have anOk
– that is, when you need to make sure that if youErr
, whatever else you're handing aResult
to *also* gets thatErr
.Notice that, unlike in [
map
](#map) or its variants, the originalresult
is not involved in constructing the newResult
.#### Examples
```ts import { and, ok, err, toString } from 'true-myth/result';
const okA = ok('A'); const okB = ok('B'); const anErr = err({ so: 'bad' });
console.log(toString(and(okB, okA))); // Ok(B) console.log(toString(and(okB, anErr))); // Err([object Object]) console.log(toString(and(anErr, okA))); // Err([object Object]) console.log(toString(and(anErr, anErr))); // Err([object Object]) ```
T The type of the value wrapped in the
Ok
of theResult
. U The type of the value wrapped in theOk
of theandResult
, i.e. the success type of theResult
present if the checkedResult
isOk
. E The type of the value wrapped in theErr
of theResult
.Parameter andResult
The
Result
instance to return ifresult
isErr
.Parameter result
The
Result
instance to check.
function andThen
andThen: { <T, E, R extends AnyResult>(thenFn: (t: T) => R, result: Result<T, E>): Result< OkFor<R>, E | ErrFor<R> >; <T, E, R extends AnyResult>(thenFn: (t: T) => R): ( result: Result<T, E> ) => Result<OkFor<R>, E | ErrFor<R>>;};
Apply a function to the wrapped value if and return a new
Ok
containing the resulting value; or if it is return it unmodified.This differs from
map
in thatthenFn
returns another . You can useandThen
to combine two functions which *both* create aResult
from an unwrapped type.You may find the
.then
method on an ES6Promise
helpful for comparison: if you have aPromise
, you can pass itsthen
method a callback which returns anotherPromise
, and the result will not be a *nested* promise, but a singlePromise
. The difference is thatPromise#then
unwraps *all* layers to only ever return a singlePromise
value, whereasResult.andThen
will not unwrap nestedResult
s.> [!NOTE] This is is sometimes also known as
bind
, but *not* aliased as such > because [bind
already means something in JavaScript][bind].[bind]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
#### Examples
```ts import { ok, err, andThen, toString } from 'true-myth/result';
const toLengthAsResult = (s: string) => ok(s.length);
const anOk = ok('just a string'); const lengthAsResult = andThen(toLengthAsResult, anOk); console.log(toString(lengthAsResult)); // Ok(13)
const anErr = err(['srsly', 'whatever']); const notLengthAsResult = andThen(toLengthAsResult, anErr); console.log(toString(notLengthAsResult)); // Err(srsly,whatever) ```
T The type of the value wrapped in the
Ok
of theResult
. U The type of the value wrapped in theOk
of theResult
returned by thethenFn
. E The type of the value wrapped in theErr
of theResult
.Parameter thenFn
The function to apply to the wrapped
T
ifmaybe
isJust
.Parameter result
The
Maybe
to evaluate and possibly apply a function to.
function ap
ap: { <A, B, E>(resultFn: Result<(a: A) => B, E>, result: Result<A, E>): Result<B, E>; <A, B, E>(resultFn: Result<(a: A) => B, E>): ( result: Result<A, E> ) => Result<B, E>;};
Allows you to *apply* (thus
ap
) a value to a function without having to take either out of the context of their s. This does mean that the transforming function is itself within aResult
, which can be hard to grok at first but lets you do some very elegant things. For example,ap
allows you to do this (using the method form, since nestingap
calls is awkward):```ts import { ap, ok, err } from 'true-myth/result';
const one = ok<number, string>(1); const five = ok<number, string>(5); const whoops = err<number, string>('oh no');
const add = (a: number) => (b: number) => a + b; const resultAdd = ok<typeof add, string>(add);
resultAdd.ap(one).ap(five); // Ok(6) resultAdd.ap(one).ap(whoops); // Err('oh no') resultAdd.ap(whoops).ap(five) // Err('oh no') ```
Without
ap
, you'd need to do something like a nestedmatch
:```ts import { ok, err } from 'true-myth/result';
const one = ok<number, string>(1); const five = ok<number, string>(5); const whoops = err<number, string>('oh no');
one.match({ Ok: n => five.match({ Ok: o => ok<number, string>(n + o), Err: e => err<number, string>(e), }), Err: e => err<number, string>(e), }); // Ok(6)
one.match({ Ok: n => whoops.match({ Ok: o => ok<number, string>(n + o), Err: e => err<number, string>(e), }), Err: e => err<number, string>(e), }); // Err('oh no')
whoops.match({ Ok: n => five.match({ Ok: o => ok(n + o), Err: e => err(e), }), Err: e => err(e), }); // Err('oh no') ```
And this kind of thing comes up quite often once you're using
Result
to handle errors throughout your application.For another example, imagine you need to compare the equality of two ImmutableJS data structures, where a
===
comparison won't work. Withap
, that's as simple as this:```ts import { ok } from 'true-myth/result'; import { is as immutableIs, Set } from 'immutable';
const is = (first: unknown) => (second: unknown) => immutableIs(first, second);
const x = ok(Set.of(1, 2, 3)); const y = ok(Set.of(2, 3, 4));
ok(is).ap(x).ap(y); // Ok(false) ```
Without
ap
, we're back to that gnarly nestedmatch
:```ts import Result, { ok, err } from 'true-myth/result'; import { is, Set } from 'immutable';
const x = ok(Set.of(1, 2, 3)); const y = ok(Set.of(2, 3, 4));
x.match({ Ok: iX => y.match({ Ok: iY => Result.of(is(iX, iY)), Err: (e) => ok(false), }) Err: (e) => ok(false), }); // Ok(false) ```
In summary: anywhere you have two
Result
instances and need to perform an operation that uses both of them,ap
is your friend.Two things to note, both regarding *currying*:
1. All functions passed to
ap
must be curried. That is, they must be of the form (for add)(a: number) => (b: number) => a + b
, *not* the more usual(a: number, b: number) => a + b
you see in JavaScript more generally.(Unfortunately, these do not currently work with lodash or Ramda's
curry
helper functions. A future update to the type definitions may make that work, but the intermediate types produced by those helpers and the more general function types expected by this function do not currently align.)2. You will need to call
ap
as many times as there are arguments to the function you're dealing with. So in the case of thisadd3
function, which has the "arity" (function argument count) of 3 (a
andb
), you'll need to callap
twice: once fora
, and once forb
. To see why, let's look at what the result in each phase is:```ts const add3 = (a: number) => (b: number) => (c: number) => a + b + c;
const resultAdd = ok(add); // Ok((a: number) => (b: number) => (c: number) => a + b + c) const resultAdd1 = resultAdd.ap(ok(1)); // Ok((b: number) => (c: number) => 1 + b + c) const resultAdd1And2 = resultAdd1.ap(ok(2)) // Ok((c: number) => 1 + 2 + c) const final = maybeAdd1.ap(ok(3)); // Ok(4) ```
So for
toString
, which just takes a single argument, you would only need to callap
once.```ts const toStr = (v: { toString(): string }) => v.toString(); ok(toStr).ap(12); // Ok("12") ```
One other scenario which doesn't come up *quite* as often but is conceivable is where you have something that may or may not actually construct a function for handling a specific
Result
scenario. In that case, you can wrap the possibly-present inap
and then wrap the values to apply to the function to inResult
themselves.Because
Result
often requires you to type out the full type parameterization on a regular basis, it's convenient to use TypeScript'stypeof
operator to write out the type of a curried function. For example, if you had a function that simply merged three strings, you might write it like this:```ts import Result from 'true-myth/result'; import { curry } from 'lodash';
const merge3Strs = (a: string, b: string, c: string) => string; const curriedMerge = curry(merge3Strs);
const fn = Result.ok<typeof curriedMerge, string>(curriedMerge); ```
The alternative is writing out the full signature long-form:
```ts const fn = Result.ok<(a: string) => (b: string) => (c: string) => string, string>(curriedMerge); ```
*Aside:**
ap
is not namedapply
because of the overlap with JavaScript's existing [apply
] function – and although strictly speaking, there isn't any direct overlap (Result.apply
andFunction.prototype.apply
don't intersect at all) it's useful to have a different name to avoid implying that they're the same.[
apply
]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/applyParameter resultFn
result of a function from T to U
Parameter result
result of a T to apply to
fn
function equals
equals: { <T, E>(resultB: Result<T, E>, resultA: Result<T, E>): boolean; <T, E>(resultB: Result<T, E>): (resultA: Result<T, E>) => boolean;};
Allows quick triple-equal equality check between the values inside two s without having to unwrap them first.
```ts const a = Result.of(3) const b = Result.of(3) const c = Result.of(null) const d = Result.nothing()
Result.equals(a, b) // true Result.equals(a, c) // false Result.equals(c, d) // true ```
Parameter resultB
A
maybe
to compare to.Parameter resultA
A
maybe
instance to check.
function isErr
isErr: <T, E>(result: Result<T, E>) => result is Err<T, E>;
Is the an ?
T The type of the item contained in the
Result
.Parameter result
The
Result
to check.Returns
A type guarded
Err
.
function isInstance
isInstance: <T, E>(item: unknown) => item is Result<T, E>;
Determine whether an item is an instance of .
Parameter item
The item to check.
function isOk
isOk: <T, E>(result: Result<T, E>) => result is Ok<T, E>;
Is the an ?
T The type of the item contained in the
Result
.Parameter result
The
Result
to check.Returns
A type guarded
Ok
.
function map
map: { <T, U, E>(mapFn: (t: T) => U, result: Result<T, E>): Result<U, E>; <T, U, E>(mapFn: (t: T) => U): (result: Result<T, E>) => Result<U, E>;};
Map over a instance: apply the function to the wrapped value if the instance is , and return the wrapped error value wrapped as a new of the correct type (
Result<U, E>
) if the instance isErr
.map
works a lot likeArray.prototype.map
, but with one important difference. BothResult
andArray
are containers for other kinds of items, but whereArray.prototype.map
has 0 to _n_ items, aResult
always has exactly one item, which is *either* a success or an error instance.Where
Array.prototype.map
will apply the mapping function to every item in the array (if there are any),Result.map
will only apply the mapping function to the (single) element if anOk
instance, if there is one.If you have no items in an array of numbers named
foo
and call `foo.map(x => x + 1)`, you'll still some have an array with nothing in it. But if you have any items in the array ([2, 3]
), and you callfoo.map(x => x + 1)
on it, you'll get a new array with each of those items inside the array "container" transformed ([3, 4]
).With this
map
, theErr
variant is treated *by themap
function* kind of the same way as the empty array case: it's just ignored, and you get back a newResult
that is still just the sameErr
instance. But if you have anOk
variant, the map function is applied to it, and you get back a newResult
with the value transformed, and still wrapped in anOk
.#### Examples
```ts import { ok, err, map, toString } from 'true-myth/result'; const double = n => n * 2;
const anOk = ok(12); const mappedOk = map(double, anOk); console.log(toString(mappedOk)); // Ok(24)
const anErr = err("nothing here!"); const mappedErr = map(double, anErr); console.log(toString(mappedErr)); // Err(nothing here!) ```
T The type of the value wrapped in an
Ok
instance, and taken as the argument to themapFn
. U The type of the value wrapped in the newOk
instance after applyingmapFn
, that is, the type returned bymapFn
. E The type of the value wrapped in anErr
instance.Parameter mapFn
The function to apply the value to if
result
isOk
.Parameter result
The
Result
instance to map over.Returns
A new
Result
with the result of applyingmapFn
to the value in anOk
, or else the originalErr
value wrapped in the new instance.
function mapErr
mapErr: { <T, E, F>(mapErrFn: (e: E) => F, result: Result<T, E>): Result<T, F>; <T, E, F>(mapErrFn: (e: E) => F): (result: Result<T, E>) => Result<T, F>;};
Map over a , exactly as in , but operating on the value wrapped in an instead of the value wrapped in the . This is handy for when you need to line up a bunch of different types of errors, or if you need an error of one shape to be in a different shape to use somewhere else in your codebase.
#### Examples
```ts import { ok, err, mapErr, toString } from 'true-myth/result';
const reason = (err: { code: number, reason: string }) => err.reason;
const anOk = ok(12); const mappedOk = mapErr(reason, anOk); console.log(toString(mappedOk)); // Ok(12)
const anErr = err({ code: 101, reason: 'bad file' }); const mappedErr = mapErr(reason, anErr); console.log(toString(mappedErr)); // Err(bad file) ```
T The type of the value wrapped in the
Ok
of theResult
. E The type of the value wrapped in theErr
of theResult
. F The type of the value wrapped in theErr
of a newResult
, returned by themapErrFn
.Parameter mapErrFn
The function to apply to the value wrapped in
Err
ifresult
is anErr
.Parameter result
The
Result
instance to map over an error case for.
function mapOr
mapOr: { <T, U, E>(orU: U, mapFn: (t: T) => U, result: Result<T, E>): U; <T, U, E>(orU: U, mapFn: (t: T) => U): (result: Result<T, E>) => U; <T, U, E>(orU: U): (mapFn: (t: T) => U) => (result: Result<T, E>) => U;};
Map over a instance as in [
map
](#map) and get out the value ifresult
is an , or return a default value ifresult
is an .#### Examples
```ts import { ok, err, mapOr } from 'true-myth/result';
const length = (s: string) => s.length;
const anOkString = ok('a string'); const theStringLength = mapOr(0, length, anOkString); console.log(theStringLength); // 8
const anErr = err('uh oh'); const anErrMapped = mapOr(0, length, anErr); console.log(anErrMapped); // 0 ```
Parameter orU
The default value to use if
result
is anErr
.Parameter mapFn
The function to apply the value to if
result
is anOk
.Parameter result
The
Result
instance to map over.
function mapOrElse
mapOrElse: { <T, U, E>(orElseFn: (err: E) => U, mapFn: (t: T) => U, result: Result<T, E>): U; <T, U, E>(orElseFn: (err: E) => U, mapFn: (t: T) => U): ( result: Result<T, E> ) => U; <T, U, E>(orElseFn: (err: E) => U): ( mapFn: (t: T) => U ) => (result: Result<T, E>) => U;};
Map over a instance as in and get out the value if
result
is , or apply a function (orElseFn
) to the value wrapped in the to get a default value.Like but using a function to transform the error into a usable value instead of simply using a default value.
#### Examples
```ts import { ok, err, mapOrElse } from 'true-myth/result';
const summarize = (s: string) =>
The response was: '${s}'
; const getReason = (err: { code: number, reason: string }) => err.reason;const okResponse = ok("Things are grand here."); const mappedOkAndUnwrapped = mapOrElse(getReason, summarize, okResponse); console.log(mappedOkAndUnwrapped); // The response was: 'Things are grand here.'
const errResponse = err({ code: 500, reason: 'Nothing at this endpoint!' }); const mappedErrAndUnwrapped = mapOrElse(getReason, summarize, errResponse); console.log(mappedErrAndUnwrapped); // Nothing at this endpoint! ```
T The type of the wrapped
Ok
value. U The type of the resulting value from applyingmapFn
to theOk
value ororElseFn
to theErr
value. E The type of the wrappedErr
value.Parameter orElseFn
The function to apply to the wrapped
Err
value to get a usable value ifresult
is anErr
.Parameter mapFn
The function to apply to the wrapped
Ok
value ifresult
is anOk
.Parameter result
The
Result
instance to map over.
function match
match: { <T, E, A>(matcher: Matcher<T, E, A>, result: Result<T, E>): A; <T, E, A>(matcher: Matcher<T, E, A>): (result: Result<T, E>) => A;};
Performs the same basic functionality as , but instead of simply unwrapping the value if it is and applying a value to generate the same default type if it is , lets you supply functions which may transform the wrapped type if it is
Ok
or get a default value forErr
.This is kind of like a poor man's version of pattern matching, which JavaScript currently lacks.
Instead of code like this:
```ts import Result, { isOk, match } from 'true-myth/result';
const logValue = (mightBeANumber: Result<number, string>) => { console.log( mightBeANumber.isOk ? mightBeANumber.value.toString() :
There was an error: ${unsafelyGetErr(mightBeANumber)}
); }; ```...we can write code like this:
```ts import Result, { match } from 'true-myth/result';
const logValue = (mightBeANumber: Result<number, string>) => { const value = match( { Ok: n => n.toString(), Err: e =>
There was an error: ${e}
, }, mightBeANumber ); console.log(value); }; ```This is slightly longer to write, but clearer: the more complex the resulting expression, the hairer it is to understand the ternary. Thus, this is especially convenient for times when there is a complex result, e.g. when rendering part of a React component inline in JSX/TSX.
Parameter matcher
A lightweight object defining what to do in the case of each variant.
Parameter result
The
result
instance to check.Performs the same basic functionality as , but instead of simply unwrapping the value if it is and applying a value to generate the same default type if it is , lets you supply functions which may transform the wrapped type if it is
Ok
or get a default value forErr
.This is kind of like a poor man's version of pattern matching, which JavaScript currently lacks.
Instead of code like this:
```ts import Result, { isOk, match } from 'true-myth/result';
const logValue = (mightBeANumber: Result<number, string>) => { console.log( mightBeANumber.isOk ? mightBeANumber.value.toString() :
There was an error: ${unsafelyGetErr(mightBeANumber)}
); }; ```...we can write code like this:
```ts import Result, { match } from 'true-myth/result';
const logValue = (mightBeANumber: Result<number, string>) => { const value = match( { Ok: n => n.toString(), Err: e =>
There was an error: ${e}
, }, mightBeANumber ); console.log(value); }; ```This is slightly longer to write, but clearer: the more complex the resulting expression, the hairer it is to understand the ternary. Thus, this is especially convenient for times when there is a complex result, e.g. when rendering part of a React component inline in JSX/TSX.
Parameter matcher
A lightweight object defining what to do in the case of each variant.
function or
or: { <T, E, F>(defaultResult: Result<T, F>, result: Result<T, E>): Result<T, F>; <T, E, F>(defaultResult: Result<T, F>): (result: Result<T, E>) => Result<T, F>;};
Provide a fallback for a given . Behaves like a logical
or
: if theresult
value is an , returns thatresult
; otherwise, returns thedefaultResult
value.This is useful when you want to make sure that something which takes a
Result
always ends up getting anOk
variant, by supplying a default value for the case that you currently have an .```ts import { ok, err, Result, or } from 'true-utils/result';
const okA = ok<string, string>('a'); const okB = ok<string, string>('b'); const anErr = err<string, string>(':wat:'); const anotherErr = err<string, string>(':headdesk:');
console.log(or(okB, okA).toString()); // Ok(A) console.log(or(anErr, okA).toString()); // Ok(A) console.log(or(okB, anErr).toString()); // Ok(B) console.log(or(anotherErr, anErr).toString()); // Err(:headdesk:) ```
T The type wrapped in the
Ok
case ofresult
. E The type wrapped in theErr
case ofresult
. F The type wrapped in theErr
case ofdefaultResult
.Parameter defaultResult
The
Result
to use ifresult
is anErr
.Parameter result
The
Result
instance to check.Returns
result
if it is anOk
, otherwisedefaultResult
.
function orElse
orElse: { <T, E, R extends AnyResult>(elseFn: (err: E) => R, result: Result<T, E>): Result< T | OkFor<R>, ErrFor<R> >; <T, E, R extends AnyResult>(elseFn: (err: E) => R): ( result: Result<T, E> ) => Result<T | OkFor<R>, ErrFor<R>>;};
Like , but using a function to construct the alternative .
Sometimes you need to perform an operation using the
error
value (and possibly other data in the environment) to construct the fallback value. In these situations, you can pass a function (which may be a closure) as theelseFn
to generate the fallbackResult<T>
. It can then transform the data in theErr
to something usable as an , or generate a new instance as appropriate.Useful for transforming failures to usable data.
Parameter elseFn
The function to apply to the contents of the
Err
ifresult
is anErr
, to create a newResult
.Parameter result
The
Result
to use if it is anOk
.Returns
The
result
if it isOk
, or theResult
returned byelseFn
ifresult
is an `Err.
function safe
safe: { <F extends AnyFunction, P extends Parameters<F>, R extends ReturnType<F>>( fn: F ): (...params: P) => Result<R, unknown>; <F extends AnyFunction, P extends Parameters<F>, R extends ReturnType<F>, E>( fn: F, handleErr: (error: unknown) => E ): (...params: P) => Result<R, E>;};
Transform a function which may throw an error into one with an identical call signature except that it will return a instead of throwing an error.
This allows you to handle the error locally with all the normal
Result
tools rather than having to catch an exception. Where the and functions are useful for a single call, this is useful to make a new version of a function to be used repeatedly.This overload absorbs all exceptions into an with the type
unknown
. If you want to transform the error immediately rather than using a combinator, see the other overload.## Examples
The
JSON.parse
method will throw if the string passed to it is invalid. You can use thissafe
method to transform it into a form which will *not* throw:```ts import { safe } from 'true-myth/task'; const parse = safe(JSON.parse);
let result = parse(
"ill-formed gobbledygook'
); console.log(result.toString()); // Err(SyntaxError: Unterminated string in JSON at position 25) ```You could do this once in a utility module and then require that *all* JSON parsing operations in your code use this version instead.
Parameter fn
The function which may throw, which will be wrapped.
Transform a function which may throw an error into one with an identical call signature except that it will return a instead of throwing an error.
This allows you to handle the error locally with all the normal
Result
tools rather than having to catch an exception. Where the and functions are useful for a single call, this is useful to make a new version of a function to be used repeatedly.This overload allows you to transform the error immediately, using the second argument.
## Examples
The
JSON.parse
method will throw if the string passed to it is invalid. You can use thissafe
method to transform it into a form which will *not* throw, wrapping it in a custom error :```ts import { safe } from 'true-myth/task';
class ParsingError extends Error { name = 'ParsingError'; constructor(error: unknown) { super('Parsing error.', { cause: error }); } }
const parse = safe(JSON.parse, (error) => { return new ParsingError(error); });
let result = parse(
"ill-formed gobbledygook'
); console.log(result.toString()); // Err(SyntaxError: Unterminated string in JSON at position 25) ```You could do this once in a utility module and then require that *all* JSON parsing operations in your code use this version instead.
Parameter fn
The function which may throw, which will be wrapped.
Parameter handleErr
A function to use to transform an unknown error into a known error type.
function toJSON
toJSON: <T, E>(result: Result<T, E>) => ResultJSON<T, E>;
Create an
Object
representation of a instance.Useful for serialization.
JSON.stringify()
uses it.Parameter result
The value to convert to JSON
Returns
The JSON representation of the
Result
function toString
toString: <T, E>(result: Result<T, E>) => string;
Create a
String
representation of a instance.An instance will be
Ok(<representation of the value>)
, and an instance will beErr(<representation of the error>)
, where the representation of the value or error is simply the value or error's owntoString
representation. For example:call | output --------------------------------- | ----------------------
toString(ok(42))
|Ok(42)
toString(ok([1, 2, 3]))
|Ok(1,2,3)
toString(ok({ an: 'object' }))
|Ok([object Object])
ntoString(err(42))
|Err(42)
toString(err([1, 2, 3]))
|Err(1,2,3)
toString(err({ an: 'object' }))
|Err([object Object])
T The type of the wrapped value; its own
.toString
will be used to print the interior contents of theJust
variant.Parameter result
The value to convert to a string.
Returns
The string representation of the
Maybe
.
function tryOr
tryOr: { <T, E>(error: E, callback: () => T): Result<T, E>; <T, E>(error: E): (callback: () => T) => Result<T, E>;};
Execute the provided callback, wrapping the return value in or if there is an exception.
```ts const aSuccessfulOperation = () => 2 + 2;
const anOkResult = Result.tryOr('Oh noes!!1', () => { aSuccessfulOperation() }); // => Ok(4)
const thisOperationThrows = () => throw new Error('Bummer');
const anErrResult = Result.tryOr('Oh noes!!1', () => { thisOperationThrows(); }); // => Err('Oh noes!!1') ```
Parameter error
The error value in case of an exception
Parameter callback
The callback to try executing
function tryOrElse
tryOrElse: { <T, E>(onError: (e: unknown) => E, callback: () => T): Result<T, E>; <T, E>(onError: (e: unknown) => E): (callback: () => T) => Result<T, E>;};
Execute the provided callback, wrapping the return value in . If there is an exception, return a of whatever the
onError
function returns.```ts import { tryOrElse } from 'true-myth/result';
const aSuccessfulOperation = () => 2 + 2;
const anOkResult = tryOrElse( (e) => e, aSuccessfulOperation ); // => Ok(4)
const thisOperationThrows = () => throw 'Bummer'
const anErrResult = tryOrElse( (e) => e, () => { thisOperationThrows(); } ); // => Err('Bummer') ```
Parameter onError
A function that takes
e
exception and returns what will be wrapped in aResult.Err
Parameter callback
The callback to try executing
function unwrapOr
unwrapOr: { <T, U, E>(defaultValue: U, result: Result<T, E>): U | T; <T, U, E>(defaultValue: U): (result: Result<T, E>) => T | U;};
Safely get the value out of the variant of a .
This is the recommended way to get a value out of a
Result
most of the time.```ts import { ok, err, unwrapOr } from 'true-myth/result';
const anOk = ok<number, string>(12); console.log(unwrapOr(0, anOk)); // 12
const anErr = err<number, string>('nooooo'); console.log(unwrapOr(0, anErr)); // 0 ```
T The value wrapped in the
Ok
. E The value wrapped in theErr
.Parameter defaultValue
The value to use if
result
is anErr
.Parameter result
The
Result
instance to unwrap if it is anOk
.Returns
The content of
result
if it is anOk
, otherwisedefaultValue
.
function unwrapOrElse
unwrapOrElse: { <T, U, E>(orElseFn: (error: E) => U, result: Result<T, E>): T | U; <T, U, E>(orElseFn: (error: E) => U): (result: Result<T, E>) => T | U;};
Safely get the value out of a by returning the wrapped value if it is , or by applying
orElseFn
to the value in the .This is useful when you need to *generate* a value (e.g. by using current values in the environment – whether preloaded or by local closure) instead of having a single default value available (as in ).
```ts import { ok, err, unwrapOrElse } from 'true-myth/result';
// You can imagine that someOtherValue might be dynamic. const someOtherValue = 2; const handleErr = (errValue: string) => errValue.length + someOtherValue;
const anOk = ok<number, string>(42); console.log(unwrapOrElse(handleErr, anOk)); // 42
const anErr = err<number, string>('oh teh noes'); console.log(unwrapOrElse(handleErr, anErr)); // 13 ```
T The value wrapped in the
Ok
. E The value wrapped in theErr
.Parameter orElseFn
A function applied to the value wrapped in
result
if it is anErr
, to generate the final value.Parameter result
The
result
to unwrap if it is anOk
.Returns
The value wrapped in
result
if it isOk
or the value returned byorElseFn
applied to the value inErr
.
interface Err
interface Err<T, E> extends Omit<ResultImpl<T, E>, 'value' | 'cast'> {}
An
Err
instance is the *failure* variant instance of the type, representing a failure outcome from an operation which may fail. For a full discussion, see the module docs.T The type which would be wrapped in an
Ok
variant ofResult
. E The type wrapped in thisErr
variant ofResult
.
interface ErrJSON
interface ErrJSON<E> {}
Representation of an when serialized to JSON.
interface Ok
interface Ok<T, E> extends Omit<ResultImpl<T, E>, 'error' | 'cast'> {}
An
Ok
instance is the *successful* variant instance of the type, representing a successful outcome from an operation which may fail. For a full discussion, see the module docs.T The type wrapped in this
Ok
variant ofResult
. E The type which would be wrapped in anErr
variant ofResult
.
interface OkJSON
interface OkJSON<T> {}
Representation of an when serialized to JSON.
interface ResultConstructor
interface ResultConstructor {}
The public interface for the class *as a value*: the static constructors
ok
anderr
produce aResult
with that variant.
type AnyResult
type AnyResult = Result<unknown, unknown>;
A convenient way to name
Result<unknown, unknown>
.
type Matcher
type Matcher<T, E, A> = { /** Transform a `T` into the resulting type `A`. */ Ok: (value: T) => A; /** Transform an `E` into the resulting type `A`. */ Err: (error: E) => A;};
A lightweight object defining how to handle each variant of a .
T The success type E The error type A The type resulting from calling on a
type Result
type Result<T, E> = Ok<T, E> | Err<T, E>;
A
Result
represents success () or failure ().The behavior of this type is checked by TypeScript at compile time, and bears no runtime overhead other than the very small cost of the container object.
type ResultJSON
type ResultJSON<T, E> = OkJSON<T> | ErrJSON<E>;
Representation of a when serialized to JSON.
type Variant
type Variant = keyof typeof Variant;
namespace task
module 'dist/task.d.ts' {}
A is a type representing an asynchronous operation that may fail, with a successful (“resolved”) value of type
T
and an error (“rejected”) value of typeE
.If the
Task
is pending, it is . If it has resolved, it is . If it has rejected, it is .For more, see [the guide](/guide/understanding/task/).
variable []
const []: any;
variable []
const []: any;
variable []
const []: any;
variable A
const A: any;
variable A
const A: any;
variable A
const A: any;
variable AnyTask
const AnyTask: any;
variable AnyTask
const AnyTask: any;
variable AnyTask
const AnyTask: any;
variable IsTask
const IsTask: Symbol;
variable PhantomData
const PhantomData: Symbol;
variable readonly
const readonly: any;
variable readonly
const readonly: any;
variable readonly
const readonly: any;
variable reject
const reject: { <T = never, E extends {} = {}>(): Task<T, Unit>; <T = never, E = unknown>(reason: E): Task<T, E>;};
Standalone function version of
variable resolve
const resolve: { <T extends Unit, E = never>(): Task<Unit, E>; <T, E = never>(value: T): Task<T, E>;};
Standalone function version of
variable RETRY_FAILED_NAME
const RETRY_FAILED_NAME: string;
variable safelyTryOr
const safelyTryOr: { <T, E>(rejection: E, fn: () => Promise<T>): Task<T, E>; <T, E>(rejection: E): (fn: () => Promise<T>) => Task<T, E>;};
An alias for for ease of migrating from v8.x to v9.x.
> [!TIP] > You should switch to . We expect to deprecate and remove > this alias at some point!
variable safelyTryOrElse
const safelyTryOrElse: { <T, E>(onError: (reason: unknown) => E, fn: () => PromiseLike<T>): Task<T, E>; <T, E>(onError: (reason: unknown) => E): ( fn: () => PromiseLike<T> ) => Task<T, E>;};
An alias for for ease of migrating from v8.x to v9.x.
> [!TIP] > You should switch to . We expect to deprecate and > remove this alias at some point!
variable State
const State: { readonly Pending: 'Pending'; readonly Resolved: 'Resolved'; readonly Rejected: 'Rejected';};
variable Task
const Task: TaskConstructor;
A
Task
is a type safe asynchronous computation.You can think of a
Task<T, E>
as being basically aPromise<Result<T, E>>
, because it *is* aPromise<Result<T, E>>
under the hood, but with two main differences from a “normal”Promise
:1. A
Task
*cannot* “reject”. All errors must be handled. This means that, like a , it will *never* throw an error if used in strict TypeScript.2. Unlike
Promise
,Task
robustly distinguishes betweenmap
andandThen
operations.Task
also implements JavaScript’sPromiseLike
interface, so you canawait
it; when aTask<T, E>
is awaited, it produces a .
variable withResolvers
const withResolvers: <T, E>() => WithResolvers<T, E>;
Standalone function version of
function all
all: (tasks: []) => Task<[], never>;
Given an array of tasks, return a new
Task
which resolves once all tasks successfully resolve or any task rejects.## Examples
Once all tasks resolve:
```ts import { all, timer } from 'true-myth/task';
let allTasks = all([ timer(10), timer(100), timer(1_000), ]);
let result = await allTasks; console.log(result.toString()); // [Ok(10,100,1000)] ```
If any tasks do *not* resolve:
```ts let { task: willReject, reject } = Task.withResolvers<never, string>();
let allTasks = all([ timer(10), timer(20), willReject, ]);
reject("something went wrong"); let result = await allTasks; console.log(result.toString()); // Err("something went wrong") ```
Parameter tasks
The list of tasks to wait on.
A The type of the array or tuple of tasks.
function allSettled
allSettled: () => any;
Given an array of tasks, return a new which resolves once all of the tasks have either resolved or rejected. The resulting
Task
is a tuple or array corresponding exactly to the tasks passed in, either resolved or rejected.## Example
Given a mix of resolving and rejecting tasks:
```ts let settledTask = allSettled([ Task.resolve<string, number>("hello"), Task.reject<number, boolean>(true), Task.resolve<{ fancy: boolean }>, Error>({ fancy: true }), ]);
let output = await settledTask; if (output.isOk) { // always true, not currently statically knowable for (let result of output.value) { console.log(result.toString()); } } ```
The resulting output will be:
``` Ok("hello"), Err(true), Ok({ fancy: true }), ```
Parameter tasks
The tasks to wait on settling.
A The type of the array or tuple of tasks.
function and
and: { <T, U, E>(andTask: Task<U, E>): (task: Task<T, E>) => Task<U, E>; <T, U, E>(andTask: Task<U, E>, task: Task<T, E>): Task<U, E>;};
Auto-curried, standalone function form of .
> [!TIP] > The auto-curried version is provided for parity with the similar functions > that the
Maybe
andResult
modules provide. However, likeResult
, you > will likely find that this form is somewhat difficult to use, because > TypeScript’s type inference does not support it well: you will tend to end > up with an awful lot ofunknown
unless you write the type parameters > explicitly at the call site. > > The non-curried form will not have that problem, so you should prefer it.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
function andThen
andThen: { <T, E, R extends AnyTask>(thenFn: (t: T) => R): ( task: Task<T, E> ) => Task<ResolvesTo<R>, E | RejectsWith<R>>; <T, U, E>(thenFn: (t: T) => U): (task: Task<T, E>) => Task<U, E>; <T, E, R extends AnyTask>(thenFn: (t: T) => R, task: Task<T, E>): Task< ResolvesTo<R>, E | RejectsWith<R> >; <T, U, E>(thenFn: (t: T) => Task<U, E>, task: Task<T, E>): Task<U, E>;};
Auto-curried, standalone function form of .
> [!TIP] > The auto-curried version is provided for parity with the similar functions > that the
Maybe
andResult
modules provide. However, likeResult
, you > will likely find that this form is somewhat difficult to use, because > TypeScript’s type inference does not support it well: you will tend to end > up with an awful lot ofunknown
unless you write the type parameters > explicitly at the call site. > > The non-curried form will not have that problem, so you should prefer it.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
function any
any: (tasks: []) => Task<never, AggregateRejection<[]>>;
Given an array of tasks, return a new which resolves once _any_ of the tasks resolves successfully, or which rejects once _all_ the tasks have rejected.
## Examples
When any item resolves:
```ts import { any, timer } from 'true-myth/task';
let anyTask = any([ timer(20), timer(10), timer(30), ]);
let result = await anyTask; console.log(result.toString()); // Ok(10); ```
When all items reject:
```ts import Task, { timer } from 'true-myth/task';
let anyTask = any([ timer(20).andThen((time) => Task.reject(
${time}ms
)), timer(10).andThen((time) => Task.reject(${time}ms
)), timer(30).andThen((time) => Task.reject(${time}ms
)), ]);let result = await anyTask; console.log(result.toString()); // Err(AggregateRejection:
Task.any
: 10ms,20ms,30ms) ```The order in the resulting
AggregateRejection
is guaranteed to be stable and to match the order of the tasks passed in.Parameter tasks
The set of tasks to check for any resolution.
Returns
A Task which is either with the value of the first task to resolve, or with the rejection reasons for all the tasks passed in in an . Note that the order of the rejection reasons is not guaranteed.
A The type of the array or tuple of tasks.
function fromPromise
fromPromise: { <T>(promise: Promise<T>): Task<T, unknown>; <T, E>(promise: Promise<T>, onRejection: (reason: unknown) => E): Task<T, E>;};
Produce a from a [
Promise
][mdn-promise].[mdn-promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
To handle the error case and produce a
Task<T, E>
instead, use the overload the overload which accepts anonRejection
handler instead.> [!IMPORTANT] > This does not (and by definition cannot) handle errors that happen during > construction of the
Promise
, because those happen before this is called. > See , , or > for alternatives which accept a callback for > constructing a promise and can therefore handle errors thrown in the call.Parameter promise
The promise from which to create the
Task
.T The type the
Promise
would resolve to, and thus that theTask
will also resolve to if thePromise
resolves.Constructors
Produce a from a [
Promise
][mdn-promise], using a .[mdn-promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
To absorb all errors/rejections as
unknown
, use the overload without anonRejection
handler instead.> [!IMPORTANT] > This does not (and by definition cannot) handle errors that happen during > construction of the
Promise
, because those happen before this is called. > See , , or > for alternatives which accept a callback for > constructing a promise and can therefore handle errors thrown in the call.Parameter promise
The promise from which to create the
Task
.Parameter onRejection
Transform errors from
unknown
to a known error type.T The type the
Promise
would resolve to, and thus that theTask
will also resolve to if thePromise
resolves. E The type of a rejectedTask
if the promise rejects.Constructors
function fromResult
fromResult: <T, E>(result: Result<T, E>) => Task<T, E>;
Build a from a .
> [!IMPORTANT] > This does not (and by definition cannot) handle errors that happen during > construction of the
Result
, because those happen before this is called. > See and as well as the corresponding > and methods for synchronous functions.## Examples
Given an ,
fromResult
will produces a task.```ts import { fromResult } from 'true-myth/task'; import { ok } from 'true-myth/result';
let successful = fromResult(ok("hello")); // -> Resolved("hello") ```
Likewise, given an
Err
,fromResult
will produces a task.```ts import { fromResult } from 'true-myth/task'; import { err } from 'true-myth/result';
let successful = fromResult(err("uh oh!")); // -> Rejected("uh oh!") ```
It is often clearest to access the function via a namespace-style import:
```ts
import * as task from 'true-myth/task'; import { ok } from 'true-myth/result';
let theTask = task.fromResult(ok(123)); ```
As an alternative, it can be useful to rename the import:
```ts import { fromResult: taskFromResult } from 'true-myth/task'; import { err } from 'true-myth/result';
let theTask = taskFromResult(err("oh no!")); ```
function fromUnsafePromise
fromUnsafePromise: <T, E>(promise: Promise<Result<T, E>>) => Task<T, E>;
Produce a
Task<T, E>
from a promise of a .> [!WARNING] > This constructor assumes you have already correctly handled the promise > rejection state, presumably by mapping it into the wrapped
Result
. It is > *unsafe* for this promise ever to reject! You should only ever use this > withPromise<Result<T, E>>
you have created yourself (including via a >Task
, of course). > > For any otherPromise<Result<T, E>>
, you should first attach acatch
> handler which will also produce aResult<T, E>
. > > If you call this with an unmanagedPromise<Result<T, E>>
, that is, one > that has *not* correctly set up acatch
handler, the rejection will > throw an error that will ***not*** be catchable > by awaiting theTask
or its originalPromise
. This can cause test > instability and unpredictable behavior in your application.Parameter promise
The promise from which to create the
Task
.Constructors
function isRetryFailed
isRetryFailed: (error: unknown) => error is RetryFailed<unknown>;
function map
map: { <T, U, E>(mapFn: (t: T) => U): (task: Task<T, E>) => Task<U, E>; <T, U, E>(mapFn: (t: T) => U, task: Task<T, E>): Task<U, E>;};
Auto-curried, standalone function form of .
> [!TIP] > The auto-curried version is provided for parity with the similar functions > that the
Maybe
andResult
modules provide. However, likeResult
, you > will likely find that this form is somewhat difficult to use, because > TypeScript’s type inference does not support it well: you will tend to end > up with an awful lot ofunknown
unless you write the type parameters > explicitly at the call site. > > The non-curried form will not have that problem, so you should prefer it.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
function mapRejected
mapRejected: { <T, E, F>(mapFn: (e: E) => F): (task: Task<T, E>) => Task<T, F>; <T, E, F>(mapFn: (e: E) => F, task: Task<T, E>): Task<T, F>;};
Auto-curried, standalone function form of .
> [!TIP] > The auto-curried version is provided for parity with the similar functions > that the
Maybe
andResult
modules provide. However, likeResult
, you > will likely find that this form is somewhat difficult to use, because > TypeScript’s type inference does not support it well: you will tend to end > up with an awful lot ofunknown
unless you write the type parameters > explicitly at the call site. > > The non-curried form will not have that problem, so you should prefer it.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
function match
match: { <T, E, A>(matcher: Matcher<T, E, A>): (task: Task<T, E>) => Promise<A>; <T, E, A>(matcher: Matcher<T, E, A>, task: Task<T, E>): Promise<A>;};
Auto-curried, standalone function form of .
> [!TIP] > The auto-curried version is provided for parity with the similar functions > that the
Maybe
andResult
modules provide. However, likeResult
, you > will likely find that this form is somewhat difficult to use, because > TypeScript’s type inference does not support it well: you will tend to end > up with an awful lot ofunknown
unless you write the type parameters > explicitly at the call site. > > The non-curried form will not have that problem, so you should prefer it.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
function or
or: { <U, F, T, E>(other: Task<U, F>): (task: Task<T, E>) => Task<T | U, F>; <U, F, T, E>(other: Task<U, F>, task: Task<T, E>): Task<U | T, F>;};
Auto-curried, standalone function form of .
> [!TIP] > The auto-curried version is provided for parity with the similar functions > that the
Maybe
andResult
modules provide. However, likeResult
, you > will likely find that this form is somewhat difficult to use, because > TypeScript’s type inference does not support it well: you will tend to end > up with an awful lot ofunknown
unless you write the type parameters > explicitly at the call site. > > The non-curried form will not have that problem, so you should prefer it.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
function orElse
orElse: { <T, E, F, U = T>(elseFn: (reason: E) => Task<U, F>): ( task: Task<T, E> ) => Task<T | U, F>; <T, E, R extends AnyTask>(elseFn: (reason: E) => R): ( task: Task<T, E> ) => Task<T | ResolvesTo<R>, RejectsWith<R>>; <T, E, F, U = T>(elseFn: (reason: E) => Task<U, F>, task: Task<T, E>): Task< T | U, F >; <T, E, R extends AnyTask>(elseFn: (reason: E) => R, task: Task<T, E>): Task< T | ResolvesTo<R>, RejectsWith<R> >;};
Auto-curried, standalone function form of .
> [!TIP] > The auto-curried version is provided for parity with the similar functions > that the
Maybe
andResult
modules provide. However, likeResult
, you > will likely find that this form is somewhat difficult to use, because > TypeScript’s type inference does not support it well: you will tend to end > up with an awful lot ofunknown
unless you write the type parameters > explicitly at the call site. > > The non-curried form will not have that problem, so you should prefer it.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
function race
race: { (tasks: []): Task<never, never>; <A extends readonly AnyTask[]>(tasks: A): Task< { -readonly [P in keyof A]: ResolvesTo<A[P]> }[number], { -readonly [P in keyof A]: RejectsWith<A[P]> }[number] >;};
Given an array of tasks, produce a new which will resolve or reject with the resolution or rejection of the *first* task which settles.
## Example
```ts import Task, { race } from 'true-myth/task';
let { task: task1, resolve } = Task.withResolvers(); let task2 = new Task((_resolve) => {}); let task3 = new Task((_resolve) => {});
resolve("Cool!"); let theResult = await race([task1, task2, task3]); console.log(theResult.toString()); // Ok("Cool!") ```
Parameter tasks
The tasks to race against each other.
A The type of the array or tuple of tasks.
function safe
safe: { < F extends (...params: never[]) => PromiseLike<unknown>, P extends Parameters<F>, R extends Awaited<ReturnType<F>> >( fn: F ): (...params: P) => Task<R, unknown>; < F extends (...params: never[]) => PromiseLike<unknown>, P extends Parameters<F>, R extends Awaited<ReturnType<F>>, E >( fn: F, onError: (reason: unknown) => E ): (...params: P) => Task<R, E>;};
Given a function which returns a
Promise
, return a new function with the same parameters but which returns a instead.If you wish to transform the error directly, rather than with a combinator, see the other overload, which accepts an error handler.
## Examples
You can use this to create a safe version of the
fetch
function, which will produce aTask
instead of aPromise
and which does not throw an error for rejections, but instead produces a variant of theTask
.```ts import { safe } from 'true-myth/task';
const fetch = safe(window.fetch); const toJson = safe((response: Response) => response.json() as unknown); let json = fetch('https://www.example.com/api/users').andThen(toJson); ```
Parameter fn
A function to wrap so it never throws an error or produces a
Promise
rejection.Given a function which returns a
Promise
and a function to transform thrown errors orPromise
rejections resulting from calling that function, return a new function with the same parameters but which returns a .To catch all errors but leave them unhandled and
unknown
, see the other overload.## Examples
You can use this to create a safe version of the
fetch
function, which will produce aTask
instead of aPromise
and which does not throw an error for rejections, but instead produces a variant of theTask
.```ts import { safe } from 'true-myth/task';
class CustomError extends Error { constructor(name: string, cause: unknown) { super(
my-lib.error.${name}
, { cause }); this.name = name; } }function handleErr(name: string): (cause: unknown) => CustomError { return (cause) => new CustomError(name); }
const fetch = safe(window.fetch, handleErr('fetch')); const toJson = safe( (response: Response) => response.toJson(), handleErr('json-parsing') );
let json = fetch('https://www.example.com/api/users').andThen(toJson); ```
Parameter fn
A function to wrap so it never throws an error or produces a
Promise
rejection.Parameter onError
A function to use to transform the
function safelyTry
safelyTry: <T>(fn: () => Promise<T>) => Task<T, unknown>;
Given a function which takes no arguments and returns a
Promise
, return a for the result of invoking that function. This safely handles functions which fail synchronously or asynchronously, so unlike is safe to use with values which may throw errors _before_ producing aPromise
.## Examples
```ts import { safelyTry } from 'true-myth/task';
function throws(): Promise { throw new Error("Uh oh!"); }
// Note: passing the function by name, *not* calling it. let theTask = safelyTry(throws); let theResult = await theTask; console.log(theResult.toString()); // Err(Error: Uh oh!) ```
Parameter fn
A function which returns a
Promise
when called.Returns
A
Task
which resolves to the resolution value of the promise or rejects with the rejection value of the promise *or* any error thrown while invokingfn
.
function safeNullable
safeNullable: { < F extends (...params: never[]) => PromiseLike<unknown>, P extends Parameters<F>, R extends Awaited<ReturnType<F>> >( fn: F ): (...params: P) => Task<Maybe<NonNullable<R>>, unknown>; < F extends (...params: never[]) => PromiseLike<unknown>, P extends Parameters<F>, R extends Awaited<ReturnType<F>>, E >( fn: F, onError: (reason: unknown) => E ): (...params: P) => Task<Maybe<NonNullable<R>>, E>;};
Given a function which returns a
Promise
of a nullable type, return a new function with the same parameters but which returns a of a instead.If you wish to transform the error directly, rather than with a combinator, see the other overload, which accepts an error handler.
This is basically just a convenience for something you could do yourself by chaining
safe
withMaybe.of
:```ts import Maybe from 'true-myth/maybe'; import { safe, safeNullable } from 'true-myth/task';
async function numberOrNull(value: number): Promise<number | null> { return Math.random() > 0.5 ? value : null; }
// Using this helper const safeNumberOrNull = safeNullable(numberOrNull);
// Using
safe
andMaybe.of
manually const moreWorkThisWay= safe(numberOrNull); let theTask = moreWorkThisWay(123).map((n) => Maybe.of(n)); ```The convenience is high, though, since you can now use this to create fully safe abstractions to use throughout your codebase, rather than having to remember to do the additional call to
map
theTask
’s resolution value into aMaybe
at each call site.Parameter fn
A function to wrap so it never throws an error or produces a
Promise
rejection.Given a function which returns a
Promise
and a function to transform thrown errors orPromise
rejections resulting from calling that function, return a new function with the same parameters but which returns a .To catch all errors but leave them unhandled and
unknown
, see the other overload.This is basically just a convenience for something you could do yourself by chaining
safe
withMaybe.of
:```ts import Maybe from 'true-myth/maybe'; import { safe, safeNullable } from 'true-myth/task';
async function numberOrNull(value: number): Promise<number | null> { return Math.random() > 0.5 ? value : null; }
// Using this helper const safeNumberOrNull = safeNullable(numberOrNull);
// Using
safe
andMaybe.of
manually const moreWorkThisWay= safe(numberOrNull); let theTask = moreWorkThisWay(123).map((n) => Maybe.of(n)); ```The convenience is high, though, since you can now use this to create fully safe abstractions to use throughout your codebase, rather than having to remember to do the additional call to
map
theTask
’s resolution value into aMaybe
at each call site.Parameter fn
A function to wrap so it never throws an error or produces a
Promise
rejection.Parameter onError
A function to use to transform the
function stopRetrying
stopRetrying: (message: string, cause?: unknown) => StopRetrying;
Produces the “sentinel”
Error
subclass , for use as a return value from .Parameter message
The message to attach to the instance.
Parameter cause
The previous cause (often another
Error
) that resulted in stopping retries.
function timeout
timeout: { <T, E>(timerOrMs: Timer | number): (task: Task<T, E>) => Task<T, E | Timeout>; <T, E>(timerOrMs: number | Timer, task: Task<T, E>): Task<T, E | Timeout>;};
Auto-curried, standalone function form of .
> [!TIP] > The auto-curried version is provided for parity with the similar functions > that the
Maybe
andResult
modules provide. However, likeResult
, you > will likely find that this form is somewhat difficult to use, because > TypeScript’s type inference does not support it well: you will tend to end > up with an awful lot ofunknown
unless you write the type parameters > explicitly at the call site. > > The non-curried form will not have that problem, so you should prefer it.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
function timer
timer: (ms: number) => Timer;
Create a which will resolve to the number of milliseconds the timer waited for that time elapses. (In other words, it safely wraps the [
setTimeout
][setTimeout] function.)[setTimeout]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout
This can be used as a “timeout” by calling it in conjunction any of the helpers like , , and so on. As a convenience to use it as a timeout for another task, you can also combine it with the instance method or the standalone function.
Provides the requested duration of the timer in case it is useful for working with multiple timers.
Parameter ms
The number of milliseconds to wait before resolving the
Task
.Returns
a Task which resolves to the passed-in number of milliseconds.
function toPromise
toPromise: <T, E>(task: Task<T, E>) => Promise<Result<T, E>>;
Standalone version of .
T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
function tryOr
tryOr: { <T, E>(rejection: E, fn: () => Promise<T>): Task<T, E>; <T, E>(rejection: E): (fn: () => Promise<T>) => Task<T, E>;};
Given a function which takes no arguments and returns a
Promise
and a value of typeE
to use as the rejection if thePromise
rejects, return a for the result of invoking that function. This safely handles functions which fail synchronously or asynchronously, so unlike is safe to use with values which may throw errors _before_ producing aPromise
.## Examples
```ts import { tryOr } from 'true-myth/task';
function throws(): Promise { throw new Error("Uh oh!"); }
// Note: passing the function by name, *not* calling it. let theTask = tryOr("fallback", throws); let theResult = await theTask; if (theResult.isErr) { console.error(theResult.error); // "fallback" } ```
You can also write this in “curried” form, passing just the fallback value and getting back a function which accepts the:
```ts import { tryOr } from 'true-myth/task';
function throws(): Promise { throw new Error("Uh oh!"); }
// Note: passing the function by name, *not* calling it. let withFallback = tryOr<number, string>("fallback"); let theResult = await withFallback(throws); if (theResult.isErr) { console.error(theResult.error); // "fallback" } ```
Note that in the curried form, you must specify the expected
T
type of the resultingTask
, or else it will always beunknown
.Parameter rejection
The value to use if the
Promise
rejects.Parameter fn
A function which returns a
Promise
when called.Returns
A
Task
which resolves to the resolution value of the promise or rejects with the rejection value of the promise *or* any error thrown while invokingfn
.
function tryOrElse
tryOrElse: { <T, E>(onError: (reason: unknown) => E, fn: () => PromiseLike<T>): Task<T, E>; <T, E>(onError: (reason: unknown) => E): ( fn: () => PromiseLike<T> ) => Task<T, E>;};
Given a function which takes no arguments and returns a
PromiseLike
and a function which accepts anunknown
rejection reason and transforms it into a known rejection typeE
, return a for the result of invoking that function. This safely handles functions which fail synchronously or asynchronously, so unlike is safe to use with values which may throw errors _before_ producing aPromise
.## Examples
```ts import { tryOrElse } from 'true-myth/task';
function throws(): Promise { throw new Error("Uh oh!"); }
// Note: passing the function by name, *not* calling it. let theTask = tryOrElse( (reason) =>
Something went wrong: ${reason}
, throws ); let theResult = await theTask; console.log(theResult.toString); // Err("Something went wrong: Error: Uh oh!") ```You can also write this in “curried” form, passing just the fallback value and getting back a function which accepts the:
```ts import { tryOrElse } from 'true-myth/task';
function throws(): Promise { throw new Error("Uh oh!"); }
// Note: passing the function by name, *not* calling it. let withFallback = tryOrElse<number, string>( (reason) =>
Something went wrong: ${reason}
); let theResult = await withFallback(throws); console.log(theResult.toString); // Err("Something went wrong: Error: Uh oh!") ```Note that in the curried form, you must specify the expected
T
type of the resultingTask
, or else it will always beunknown
.Parameter onError
The function to use to transform the rejection reasons if the
PromiseLike
produced byfn
rejects.Parameter fn
A function which returns a
PromiseLike
when called.Returns
A
Task
which resolves to the resolution value of the promise or rejects with the rejection value of the promise *or* any error thrown while invokingfn
.
function withRetries
withRetries: <T, E>( retryable: (status: RetryStatus) => Task<T, E | StopRetrying> | StopRetrying, strategy?: delay.Strategy) => Task<T, RetryFailed<E>>;
Execute a callback that produces either a or the “sentinel” [
Error
][error-mdn] subclass .withRetries
retries theretryable
callback until the retry strategy is exhausted *or* until the callback returns eitherStopRetrying
or aTask
that rejects withStopRetrying
. If no strategy is supplied, a default strategy of retrying immediately up to three times is used.[error-mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
The
strategy
is any iterable iterator that produces an integral number, which is used as the number of milliseconds to delay before retrying theretryable
. When thestrategy
stops yielding values, this will produce aTask
whose rejection value is an instance of .Returning
stopRetrying()
from the top-level of the function or as the rejection reason will also produce a rejectedTask
whose rejection value is an instance ofRetryFailed
, but will also immediately stop all further retries and will include theStopRetrying
instance as thecause
of theRetryFailed
instance.You can determine whether retries stopped because the strategy was exhausted or because
stopRetrying
was called by checking thecause
on theRetryFailed
instance. It will beundefined
if the theRetryFailed
was the result of the strategy being exhausted. It will be aStopRetrying
if it stopped because the caller returnedstopRetrying()
.## Examples
### Retrying with backoff
When attempting to fetch data from a server, you might want to retry if and only if the response was an HTTP 408 response, indicating that there was a timeout but that the client is allowed to try again. For other error codes, it will simply reject immediately.
```ts import * as task from 'true-myth/task'; import * as delay from 'true-myth/task/delay';
let theTask = withRetries( () => task.fromPromise(fetch('https://example.com')).andThen((res) => { if (res.status === 200) { return task.fromPromise(res.json()); } else if (res.status === 408) { return task.reject(res.statusText); } else { return task.stopRetrying(res.statusText); } }), delay.fibonacci().map(delay.jitter).take(10) ); ```
Here, this uses a Fibonacci backoff strategy, which can be preferable in some cases to a classic exponential backoff strategy (see [A Performance Comparison of Different Backoff Algorithms under Different Rebroadcast Probabilities for MANET's][pdf] for more details).
[pdf]: https://www.researchgate.net/publication/255672213_A_Performance_Comparison_of_Different_Backoff_Algorithms_under_Different_Rebroadcast_Probabilities_for_MANET's
### Manually canceling retries
Sometimes, you may determine that the result of an operation is fatal, so there is no point in retrying even if the retry strategy still allows it. In that case, you can return the special
StopRetrying
error produced by callingstopRetrying
to immediately stop all further retries.For example, imagine you have a library function that returns a custom
Error
subclass that includes anisFatal
value on it, something like this::```ts class AppError extends Error { isFatal: boolean; constructor(message: string, options?: { isFatal?: boolean, cause?: unknown }) { super(message, { cause: options?.cause }); this.isFatal = options?.isFatal ?? false; } } ```
You could check that flag in a
Task
rejection and returnstopRetrying()
if it is set:```ts import * as task from 'true-myth/task'; import { fibonacci, jitter } from 'true-myth/task/delay'; import { doSomethingThatMayFailWithAppError } from 'someplace/in/my-app';
let theTask = task.withRetries( () => { doSomethingThatMayFailWithAppError().orElse((rejection) => { if (rejection.isFatal) { return task.stopRetrying("It was fatal!", { cause: rejection }); }
return task.reject(rejection); }); }, fibonacci().map(jitter).take(20) ); ```
### Using the retry
status
parameterEvery time
withRetries
tries theretryable
, it provides the current count of attempts and the total elapsed duration as properties on thestatus
object, so you can do different things for a given way of trying the async operation represented by theTask
depending on the count. Here, for example, the task is retried if the HTTP request rejects, with an exponential backoff starting at 100 milliseconds, and captures the number of retries in anError
wrapping the rejection reason when the response rejects or when converting the response to JSON fails. It also stops if it has tried the call more than 10 times or if the total elapsed time exceeds 10 seconds.```ts import * as task from 'true-myth/task'; import { exponential, jitter } from 'true-myth/task/delay';
let theResult = await task.withRetries( ({ count, elapsed }) => { if (count > 10) { return task.stopRetrying(
Tried too many times: ${count}
); }if (elapsed > 10_000) { return task.stopRetrying(
Took too long: ${elapsed}ms
); }return task.fromPromise(fetch('https://www.example.com/')) .andThen((res) => task.fromPromise(res.json())) .orElse((cause) => { let message =
Attempt #${count} failed
; return task.reject(new Error(message, { cause })); }); }, exponential().map(jitter), ); ```### Custom strategies
While the module supplies a number of useful strategies, you can also supply your own. The easiest way is to write [a generator function][gen], but you can also implement a custom iterable iterator, including by providing a subclass of the ES2025
Iterator
class.Here is an example of using a generator function to produce a random but [monotonically increasing][monotonic] value proportional to the current value:
```ts import * as task from 'true-myth/task';
function* randomIncrease(options?: { from: number }) { // always use integral values, and default to one second. let value = options ? Math.round(options.from) : 1_000; while (true) { yield value; value += Math.ceil(Math.random() * value); // always increase! } }
await task.withRetries(({ count }) => { let delay = Math.round(Math.random() * 100); return task.timer(delay).andThen((time) => task.reject(
Rejection #${count} after ${time}ms
), ); }, randomIncrease(10).take(10)); ```[monotonic]: https://en.wikipedia.org/wiki/Monotonic_function
Parameter retryable
A callback that produces a .
Parameter strategy
An iterable iterator that produces an integral number of milliseconds to wait before trying
retryable
again. If not supplied, theretryable
will be retried immediately up to three times.T The type of a . E The type of a .
class AggregateRejection
class AggregateRejection<E extends unknown[]> extends Error {}
An error type produced when produces any rejections. All rejections are aggregated into this type.
> [!NOTE] > This error type is not allowed to be subclassed.
E The type of the rejection reasons.
constructor
constructor(errors: unknown[]);
property errors
readonly errors: unknown[];
property name
readonly name: string;
method toString
toString: () => string;
class InvalidAccess
class InvalidAccess extends Error {}
constructor
constructor(field: 'value' | 'reason', state: State);
property name
readonly name: string;
class RetryFailed
class RetryFailed<E> extends Error {}
An [
Error
][mdn-error] subclass for when aTask
rejected after a specified number of retries. It includes all rejection reasons, including the final one, as well as the number of retries and the total duration spent on the retries. It distinguishes between the list of rejections and the optionalcause
property inherited fromError
so that it can indicate if the retries failed because the retry strategy was exhausted (in which casecause
will beundefined
) or because the caller returned a instance (in which casecause
will be that instance.)You can neither construct nor subclass this error, only use its properties. If you need to check whether an
Error
class is an instance of this class, you can check whether itsname
is or you can use the helper function:```ts import * as task from 'true-myth/task';
// snip let result = await someFnThatReturnsATask(); if (result.isErr) { if (task.isRetryFailed(result.error)) { if (result.error.cause) { console.error('You quit on purpose: ', cause); }
for (let rejection of result.error.rejections) { console.error(rejection); } } else { // handle other error types } } ```
[mdn-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
Errors
property name
readonly name: string;
property rejections
readonly rejections: E[];
Set of all rejections collected during retries.
property totalDuration
readonly totalDuration: number;
Elapsed time in milliseconds.
property tries
readonly tries: number;
Number of retries before the task failed.
class StopRetrying
class StopRetrying extends Error {}
A custom [
Error
][mdn-error] subclass which acts as a “sentinel”: when you return it either as the top-level return value from the callback for or the rejection reason for a produces bywithRetries
, the function will stop retrying immediately.[mdn-error]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error
You can neither construct this class directly nor subclass it. Instead, use the helper function to construct it.
Errors
property name
readonly name: string;
class TaskExecutorException
class TaskExecutorException extends Error {}
The error thrown when an error is thrown in the executor passed to . This error class exists so it is clear exactly what went wrong in that case.
Errors
constructor
constructor(originalError: {});
property name
name: string;
class Timeout
class Timeout extends Error {}
An
Error
type representing a timeout, as when a elapses.
constructor
constructor(ms: number);
property duration
readonly duration: number;
property ms
readonly ms: number;
class UnsafePromise
class UnsafePromise extends Error {}
An error thrown when the
Promise<Result<T, E>>
passed to fromUnsafePromise rejects.Errors
constructor
constructor(unhandledError: {});
property name
readonly name: string;
interface Pending
interface Pending<T, E> extends Omit<TaskImpl<T, E>, 'value' | 'reason'> {}
A that has not yet resolved.
T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.Task Variants
index signature
get isPending(): true;
index signature
get isResolved(): false;
index signature
get isRejected(): false;
index signature
get state(): typeof State.Pending;
interface Rejected
interface Rejected<T, E> extends Omit<TaskImpl<T, E>, 'value'> {}
A that has rejected. Its
reason
is of typeE
.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.Task Variants
index signature
get isPending(): false;
index signature
get isResolved(): false;
index signature
get isRejected(): true;
index signature
get state(): typeof State.Rejected;
index signature
get reason(): E;
interface Resolved
interface Resolved<T, E> extends Omit<TaskImpl<T, E>, 'reason'> {}
A that has resolved. Its
value
is of typeT
.T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.Task Variants
index signature
get isPending(): false;
index signature
get isResolved(): true;
index signature
get isRejected(): false;
index signature
get state(): typeof State.Resolved;
index signature
get value(): T;
interface RetryStatus
interface RetryStatus {}
Information about the current retryable call status.
interface TaskConstructor
interface TaskConstructor extends Omit<typeof TaskImpl, 'constructor'> {}
The public interface for the class *as a value*: a constructor and the associated static properties.
construct signature
new <T, E>( executor: (resolve: (value: T) => void, reject: (reason: E) => void) => void): Task<T, E>;
Construct a new
Task
, using callbacks to wrap APIs which do not natively provide aPromise
.This is identical to the [Promise][promise] constructor, with one very important difference: rather than producing a value upon resolution and throwing an exception when a rejection occurs like
Promise
, aTask
always “succeeds” in producing a usable value, just like for synchronous code.[promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
For constructing a
Task
from an existingPromise
, see:- - - -
For constructing a
Task
immediately resolved or rejected with given values, see and respectively.Parameter executor
A function which the constructor will execute to manage the lifecycle of the
Task
. The executor in turn has two functions as parameters: one to call on resolution, the other on rejection.
type Matcher
type Matcher<T, E, A> = { Resolved: (value: T) => A; Rejected: (reason: E) => A;};
A lightweight object defining how to handle each outcome state of a .
type Task
type Task<T, E> = Pending<T, E> | Resolved<T, E> | Rejected<T, E>;
A
Task
is a type safe asynchronous computation.You can think of a
Task<T, E>
as being basically aPromise<Result<T, E>>
, because it *is* aPromise<Result<T, E>>
under the hood, but with two main differences from a “normal”Promise
:1. A
Task
*cannot* “reject”. All errors must be handled. This means that, like a , it will *never* throw an error if used in strict TypeScript.2. Unlike
Promise
,Task
robustly distinguishes betweenmap
andandThen
operations.Task
also implements JavaScript’sPromiseLike
interface, so you canawait
it; when aTask<T, E>
is awaited, it produces a .T The type of the value when the
Task
resolves successfully. E The type of the rejection reason when theTask
rejects.
type TaskTypesFor
type TaskTypesFor<A extends readonly AnyTask[]> = { resolution: { -readonly [P in keyof A]: ResolvesTo<A[P]>; }; rejection: { -readonly [P in keyof A]: RejectsWith<A[P]>; };};
type Timer
type Timer = Task<number, never>;
A specialized for use with or other methods or functions which want to know they are using.
> [!NOTE] > This type has zero runtime overhead, including for construction: it is just > a
Task
with additional *type information*.
type WithResolvers
type WithResolvers<T, E> = { task: Task<T, E>; resolve: (value: T) => void; reject: (reason: E) => void;};
Type returned by calling
namespace delay
module 'dist/task/delay.d.ts' {}
Types and helpers for managing retry delays with the
withRetries
helper fromTask
.
function exponential
exponential: (options?: { from?: number; withFactor?: number;}) => Generator<number>;
Generate an infinite iterable of integers beginning with
base
and increasing exponentially until reachingNumber.MAX_SAFE_INTEGER
, after which the generator will continue yieldingNumber.MAX_SAFE_INTEGER
forever.By default, this increases exponentially by a factor of 2; you may optionally pass
{ factor: someOtherValue }
to change the exponentiation factor.If you pass a non-integral value as
base
, it will be rounded to the nearest integral value usingMath.round
.
function fibonacci
fibonacci: (options?: { from: number }) => Generator<number>;
Generate an infinite iterable of integers beginning with
base
and increasing as a Fibonacci sequence (1, 1, 2, 3, 5, 8, 13, ...) until reachingNumber.MAX_SAFE_INTEGER
, after which the generator will continue yieldingNumber.MAX_SAFE_INTEGER
forever.If you pass a non-integral value as the
from
property on the configuration argument, it will be rounded to the nearest integral value usingMath.round
.
function fixed
fixed: (options?: { at: number }) => Generator<number>;
Generate an infinite iterable of the same integer value in milliseconds.
If you pass a non-integral value, like
{ at: 2.5 }
, it will be rounded to the nearest integral value usingMath.round
, i.e.3
in that case.
function immediate
immediate: () => Generator<number>;
Generate an infinite iterable of the value
0
.
function jitter
jitter: (n: number) => number;
Apply fully random jitter proportional to the number passed in. The resulting value will never be larger than 2×n, and never less than 0.
This is useful for making sure your retries generally follow a given , but if multiple tasks start at the same time, they do not all retry at exactly the same time.
Parameter n
The value to apply random jitter to.
function linear
linear: (options?: { from?: number; withStepSize?: number;}) => Generator<number>;
Generate an infinite iterable of integers beginning with
base
and increasing linearly (1, 2, 3, 4, 5, 5, 7, ...) until reachingNumber.MAX_SAFE_INTEGER
, after which the generator will continue yieldingNumber.MAX_SAFE_INTEGER
forever.By default, this increases by a step size of 1; you may optionally pass
{ step: someOtherValue }
to change the step size.If you pass a non-integral value as
base
, it will be rounded to the nearest integral value usingMath.round
.
function none
none: () => Generator<number>;
A “no-op” strategy, for if you need to call supply a to a function but do not actually want to retry at all.
You should never use this directly with
Task.withRetries
; in the case where you would, invoke theTask
that would be retried directly (i.e. without usingwithRetries
at all) instead.
interface Strategy
interface Strategy extends Iterator<number> {}
A retry delay strategy is just an
Iterator<number>
.For details on how to use or implement a
Strategy
, as well as why it exists as a distinct type, see [the guide][guide].[guide]: /guide/understanding/task/retries-and-delays
namespace Delay
module 'dist/task/delay.d.ts' {}
Types and helpers for managing retry delays with the
withRetries
helper fromTask
.
function exponential
exponential: (options?: { from?: number; withFactor?: number;}) => Generator<number>;
Generate an infinite iterable of integers beginning with
base
and increasing exponentially until reachingNumber.MAX_SAFE_INTEGER
, after which the generator will continue yieldingNumber.MAX_SAFE_INTEGER
forever.By default, this increases exponentially by a factor of 2; you may optionally pass
{ factor: someOtherValue }
to change the exponentiation factor.If you pass a non-integral value as
base
, it will be rounded to the nearest integral value usingMath.round
.
function fibonacci
fibonacci: (options?: { from: number }) => Generator<number>;
Generate an infinite iterable of integers beginning with
base
and increasing as a Fibonacci sequence (1, 1, 2, 3, 5, 8, 13, ...) until reachingNumber.MAX_SAFE_INTEGER
, after which the generator will continue yieldingNumber.MAX_SAFE_INTEGER
forever.If you pass a non-integral value as the
from
property on the configuration argument, it will be rounded to the nearest integral value usingMath.round
.
function fixed
fixed: (options?: { at: number }) => Generator<number>;
Generate an infinite iterable of the same integer value in milliseconds.
If you pass a non-integral value, like
{ at: 2.5 }
, it will be rounded to the nearest integral value usingMath.round
, i.e.3
in that case.
function immediate
immediate: () => Generator<number>;
Generate an infinite iterable of the value
0
.
function jitter
jitter: (n: number) => number;
Apply fully random jitter proportional to the number passed in. The resulting value will never be larger than 2×n, and never less than 0.
This is useful for making sure your retries generally follow a given , but if multiple tasks start at the same time, they do not all retry at exactly the same time.
Parameter n
The value to apply random jitter to.
function linear
linear: (options?: { from?: number; withStepSize?: number;}) => Generator<number>;
Generate an infinite iterable of integers beginning with
base
and increasing linearly (1, 2, 3, 4, 5, 5, 7, ...) until reachingNumber.MAX_SAFE_INTEGER
, after which the generator will continue yieldingNumber.MAX_SAFE_INTEGER
forever.By default, this increases by a step size of 1; you may optionally pass
{ step: someOtherValue }
to change the step size.If you pass a non-integral value as
base
, it will be rounded to the nearest integral value usingMath.round
.
function none
none: () => Generator<number>;
A “no-op” strategy, for if you need to call supply a to a function but do not actually want to retry at all.
You should never use this directly with
Task.withRetries
; in the case where you would, invoke theTask
that would be retried directly (i.e. without usingwithRetries
at all) instead.
interface Strategy
interface Strategy extends Iterator<number> {}
A retry delay strategy is just an
Iterator<number>
.For details on how to use or implement a
Strategy
, as well as why it exists as a distinct type, see [the guide][guide].[guide]: /guide/understanding/task/retries-and-delays
namespace toolbelt
module 'dist/toolbelt.d.ts' {}
Tools for working easily with
Maybe
andResult
*together*... but which do not *require* you to use both. If they were in thetrue-myth/maybe
ortrue-myth/result
modules, then importing either would always include the other. While that is not usually a concern with bundlers, it *is* an issue when using dynamic imports or otherwise doing runtime resolution in a browser or similar environment.The flip side of that is: importing from *this* module *does* require access to both
Maybe
andResult
modules.
function fromMaybe
fromMaybe: { <T, E>(errValue: E, maybe: Maybe<T>): Result<T, E>; <T, E>(errValue: E): (maybe: Maybe<T>) => Result<T, E>;};
Transform a into a .
If the
Maybe
is a , its value will be wrapped in the variant; if it is a , theerrValue
will be wrapped in the variant.Parameter errValue
A value to wrap in an
Err
ifmaybe
is aNothing
.Parameter maybe
The
Maybe
to convert to aResult
.
function fromResult
fromResult: <T extends {}>(result: Result<T, unknown>) => Maybe<T>;
Construct a from a .
If the
Result
is a , wrap its value in . If theResult
is an , throw away the wrappedE
and transform to a .T The type of the value wrapped in a and therefore in the of the resulting
Maybe
.Parameter result
The
Result
to construct aMaybe
from.Returns
Just
ifresult
wasOk
orNothing
if it wasErr
.
function toMaybe
toMaybe: <T extends {}>(result: Result<T, unknown>) => Maybe<T>;
Convert a to a .
The converted type will be if the
Result
is or if theResult
is ; the wrapped error value will be discarded.Parameter result
The
Result
to convert to aMaybe
Returns
Just
the value inresult
if it isOk
; otherwiseNothing
function toOkOrElseErr
toOkOrElseErr: { <T, E>(elseFn: () => E, maybe: Maybe<T>): Result<T, E>; <T, E>(elseFn: () => E): (maybe: Maybe<T>) => Result<T, E>;};
Transform the into a , using the wrapped value as the value if the
Maybe
is ; otherwise usingelseFn
to generate the .T The wrapped value. E The error type to in the
Result
.Parameter elseFn
The function which generates an error of type
E
.Parameter maybe
The
Maybe
instance to convert.Returns
A
Result
containing the value wrapped inmaybe
in anOk
, or the value generated byelseFn
in anErr
.
function toOkOrErr
toOkOrErr: { <T, E>(error: E, maybe: Maybe<T>): Result<T, E>; <T, E>(error: E): (maybe: Maybe<T>) => Result<T, E>;};
Transform the into a , using the wrapped value as the value if the
Maybe
is ; otherwise using the suppliederror
value for .T The wrapped value. E The error type to in the
Result
.Parameter error
The error value to use if the
Maybe
isNothing
.Parameter maybe
The
Maybe
instance to convert.Returns
A
Result
containing the value wrapped inmaybe
in anOk
, orerror
in anErr
.
function transposeMaybe
transposeMaybe: <T extends {}, E>( maybe: Maybe<Result<T, E>>) => Result<Maybe<T>, E>;
Transposes a of a into a
Result
of aMaybe
.| Input | Output | | -------------- | ------------- | |
Just(Ok(T))
|Ok(Just(T))
| |Just(Err(E))
|Err(E)
| |Nothing
|Ok(Nothing)
|Parameter maybe
a
Maybe<Result<T, E>>
to transform to aResult<Maybe<T>, E>>
.
function transposeResult
transposeResult: <T, E>(result: Result<Maybe<T>, E>) => Maybe<Result<T, E>>;
Transposes a of a into a
Maybe
of aResult
.| Input | Output | | ------------- | -------------- | |
Ok(Just(T))
|Just(Ok(T))
| |Err(E)
|Just(Err(E))
| |Ok(Nothing)
|Nothing
|Parameter result
a
Result<Maybe<T>, E>
to transform to aMaybe<Result<T, E>>
.
Package Files (7)
Dependencies (0)
No dependencies.
Dev Dependencies (19)
Peer Dependencies (0)
No peer dependencies.
Badge
To add a badge like this oneto your package's README, use the codes available below.
You may also use Shields.io to create a custom badge linking to https://www.jsdocs.io/package/true-myth
.
- Markdown[](https://www.jsdocs.io/package/true-myth)
- HTML<a href="https://www.jsdocs.io/package/true-myth"><img src="https://img.shields.io/badge/jsDocs.io-reference-blue" alt="jsDocs.io"></a>
- Updated .
Package analyzed in 9173 ms. - Missing or incorrect documentation? Open an issue for this package.