Interface Pending<T, E>

A Task<T, E> that has not yet resolved.

interface Pending<T, E> {
    get isPending(): true;
    get isRejected(): false;
    get isResolved(): false;
    get state(): "Pending";
    and<U, F = E>(other: Task<U, F>): Task<U, E | F>;
    andThen<U, F = E>(thenFn: (t: T) => Task<U, F>): Task<U, E | F>;
    map<U>(mapFn: (t: T) => U): Task<U, E>;
    mapRejected<F>(mapFn: (e: E) => F): Task<T, F>;
    match<A>(matcher: Matcher<T, E, A>): Promise<A>;
    or<F, U = T>(other: Task<U, F>): Task<T | U, F>;
    orElse<F, U = T>(elseFn: (reason: E) => Task<U, F>): Task<T | U, F>;
    then<A, B>(
        onSuccess?: (result: Result<T, E>) => A | PromiseLike<A>,
        onRejected?: (reason: unknown) => B | PromiseLike<B>,
    ): PromiseLike<A | B>;
    timeout(timerOrMs: number | Timer): Task<T, E | Timeout>;
    toPromise(): Promise<Result<T, E>>;
    toString(): string;
}

Type Parameters

  • T

    The type of the value when the Task resolves successfully.

  • E

    The type of the rejection reason when the Task rejects.

Hierarchy

  • Omit<TaskImpl<T, E>, "value" | "reason">
    • Pending

Accessors

  • get isPending(): true
  • Returns true

  • get isRejected(): false
  • Returns false

  • get isResolved(): false
  • Returns false

  • get state(): "Pending"
  • Returns "Pending"

Methods

  • You can think of this like a short-circuiting logical "and" operation on a Task. If this task resolves, then the output is the task passed to the method. If this task rejects, the result is its rejection reason.

    This is useful when you have another Task value you want to provide if and only if the first task resolves successfully – that is, when you need to make sure that if you reject, whatever else you're handing a Task to also gets that Rejected.

    Notice that, unlike in Task.prototype.map, the original task resolution value is not involved in constructing the new Task.

    let resolvedA = Task.resolved<string, string>('A');
    let resolvedB = Task.resolved<string, string>('B');
    let rejectedA = Task.rejected<string, string>('bad');
    let rejectedB = Task.rejected<string, string>('lame');

    let aAndB = resolvedA.and(resolvedB);
    await aAndB;

    let aAndRA = resolvedA.and(rejectedA);
    await aAndRA;

    let raAndA = rejectedA.and(resolvedA);
    await raAndA;

    let raAndRb = rejectedA.and(rejectedB);
    await raAndRb;

    expect(aAndB.toString()).toEqual('Task.Resolved("B")');
    expect(aAndRA.toString()).toEqual('Task.Rejected("bad")');
    expect(raAndA.toString()).toEqual('Task.Rejected("bad")');
    expect(raAndRb.toString()).toEqual('Task.Rejected("bad")');

    Type Parameters

    • U

      The type of the value for a resolved version of the other Task, i.e., the success type of the final Task present if the first Task is Ok.

    • F = E

    Parameters

    • other: Task<U, F>

      The Task instance to return if this is Rejected.

    Returns Task<U, E | F>

  • Apply a function to the resulting value if a Task is Resolved, producing a new Task; or if it is Rejected return the rejection reason unmodified.

    This differs from map in that thenFn returns another Task. You can use andThen to combine two functions which both create a Task from an unwrapped type.

    The Promise.prototype.then method is a helpful comparison: if you have a Promise, you can pass its then method a callback which returns another Promise, and the result will not be a nested promise, but a single Promise. The difference is that Promise.prototype.then unwraps all layers to only ever return a single Promise value, whereas this method will not unwrap nested Tasks.

    Promise.prototype.then also acts the same way Task.prototype.map does, while Task distinguishes map from andThen.

    Note

    andThen is sometimes also known as bind, but not aliased as such because bind already means something in JavaScript.

    import Task from 'true-myth/task';

    const toLengthAsResult = (s: string) => ok(s.length);

    const aResolvedTask = Task.resolved('just a string');
    const lengthAsResult = await aResolvedTask.andThen(toLengthAsResult);
    console.log(lengthAsResult.toString()); // Ok(13)

    const aRejectedTask = Task.rejected(['srsly', 'whatever']);
    const notLengthAsResult = await aRejectedTask.andThen(toLengthAsResult);
    console.log(notLengthAsResult.toString()); // Err(srsly,whatever)

    Type Parameters

    • U

      The type of the value produced by the new Task of the Result returned by the thenFn.

    • F = E

    Parameters

    • thenFn: (t: T) => Task<U, F>

      The function to apply to the wrapped T if maybe is Just.

    Returns Task<U, E | F>

  • Map over a Task instance: apply the function to the resolved value if the task completes successfully, producing a new Task with the value returned from the function. If the task failed, return the rejection as Rejected without modification.

    map works a lot like Array.prototype.map, but with one important difference. Both Task and Array are kind of like a “container” for other kinds of items, but where Array.prototype.map has 0 to n items, a Task represents the possibility of an item being available at some point in the future, and when it is present, it is either a success or an error.

    Where Array.prototype.map will apply the mapping function to every item in the array (if there are any), Task.map will only apply the mapping function to the resolved element if it is Resolved.

    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 call foo.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, the Rejected variant is treated by the map function kind of the same way as the empty array case: it's just ignored, and you get back a new Task that is still just the same Rejected instance. But if you have an Resolved variant, the map function is applied to it, and you get back a new Task with the value transformed, and still Resolved.

    import Task from 'true-myth/task';
    const double = n => n * 2;

    const aResolvedTask = Task.resolved(12);
    const mappedResolved = aResolvedTask.map(double);
    let resolvedResult = await aResolvedTask;
    console.log(resolvedResult.toString()); // Ok(24)

    const aRejectedTask = Task.rejected("nothing here!");
    const mappedRejected = map(double, aRejectedTask);
    let rejectedResult = await aRejectedTask;
    console.log(rejectedResult.toString()); // Err("nothing here!")

    Type Parameters

    • U

      The type of the resolved value of the returned Task.

    Parameters

    • mapFn: (t: T) => U

      The function to apply the value to when the Task finishes if it is Resolved.

    Returns Task<U, E>

  • Map over a Task, exactly as in map, but operating on the rejection reason if the Task rejects, producing a new Task, still rejected, with the value returned from the function. If the task completed successfully, return it as Resolved without modification. 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.

    import Task from 'true-myth/task';

    const extractReason = (err: { code: number, reason: string }) => err.reason;

    const aResolvedTask = Task.resolved(12);
    const mappedResolved = aResolvedTask.mapErr(extractReason);
    console.log(mappedOk)); // Ok(12)

    const aRejectedTask = Task.rejected({ code: 101, reason: 'bad file' });
    const mappedRejection = await aRejectedTask.map(extractReason);
    console.log(toString(mappedRejection)); // Err("bad file")

    Type Parameters

    • F

      The type of the rejection for the new Task, returned by the mapFn.

    Parameters

    • mapFn: (e: E) => F

      The function to apply to the rejection reason if the Task is rejected.

    Returns Task<T, F>

  • Allows you to produce a new value by providing functions to operate against both the Resolved and Rejected states once the Task resolves.

    (This is a workaround for JavaScript’s lack of native pattern-matching.)

    import Task from 'true-myth/task';

    let theTask = new Task<number, Error>((resolve, reject) => {
    let value = Math.random();
    if (value > 0.5) {
    resolve(value);
    } else {
    reject(new Error(`too low: ${value}`));
    }
    });

    // Note that we are here awaiting the `Promise` returned from the `Task`,
    // not the `Task` itself.
    await theTask.match({
    Resolved: (num) => {
    console.log(num);
    },
    Rejected: (err) => {
    console.error(err);
    },
    });

    This can both be used to produce side effects (as here) and to produce a value regardless of the resolution/rejection of the task, and is often clearer than trying to use other methods. Thus, this is especially convenient for times when there is a complex task output.

    Note

    You could also write the above example like this, taking advantage of how awaiting a Task produces its inner Result:

    import Task from 'true-myth/task';

    let theTask = new Task<number, Error>((resolve, reject) => {
    let value = Math.random();
    if (value > 0.5) {
    resolve(value);
    } else {
    reject(new Error(`too low: ${value}`));
    }
    });

    let theResult = await theTask;
    theResult.match({
    Ok: (num) => {
    console.log(num);
    },
    Err: (err) => {
    console.error(err);
    },
    });

    Which of these you choose is a matter of taste!

    Type Parameters

    • A

    Parameters

    • matcher: Matcher<T, E, A>

      A lightweight object defining what to do in the case of each variant.

    Returns Promise<A>

  • Provide a fallback for a given Task. Behaves like a logical or: if the task value is Resolved, returns that task unchanged, otherwise, returns the other Task.

    This is useful when you want to make sure that something which takes a Task always ends up getting a Resolved variant, by supplying a default value for the case that you currently have an Rejected.

    import Task from 'true-utils/task';

    const resolvedA = Task.resolved<string, string>('a');
    const resolvedB = Task.resolved<string, string>('b');
    const rejectedWat = Task.rejected<string, string>(':wat:');
    const rejectedHeaddesk = Task.rejected<string, string>(':headdesk:');

    console.log(resolvedA.or(resolvedB).toString()); // Resolved("a")
    console.log(resolvedA.or(rejectedWat).toString()); // Resolved("a")
    console.log(rejectedWat.or(resolvedB).toString()); // Resolved("b")
    console.log(rejectedWat.or(rejectedHeaddesk).toString()); // Rejected(":headdesk:")

    Type Parameters

    • F

      The type wrapped in the Rejected case of other.

    • U = T

    Parameters

    • other: Task<U, F>

      The Result to use if this is Rejected.

    Returns Task<T | U, F>

    this if it is Resolved, otherwise other.

  • Like or, but using a function to construct the alternative Task.

    Sometimes you need to perform an operation using the rejection reason (and possibly also other data in the environment) to construct a new Task, which may itself resolve or reject. In these situations, you can pass a function (which may be a closure) as the elseFn to generate the fallback Task<T, E>. It can then transform the data in the Rejected to something usable as an Resolved, or generate a new Rejected instance as appropriate.

    Useful for transforming failures to usable data, for trigger retries, etc.

    Type Parameters

    • F
    • U = T

    Parameters

    • elseFn: (reason: E) => Task<U, F>

      The function to apply to the Rejection reason if the Task rejects, to create a new Task.

    Returns Task<T | U, F>

  • Attaches callbacks for the resolution and/or rejection of the Promise.

    Type Parameters

    • A
    • B

    Parameters

    • OptionalonSuccess: (result: Result<T, E>) => A | PromiseLike<A>
    • OptionalonRejected: (reason: unknown) => B | PromiseLike<B>

    Returns PromiseLike<A | B>

    A Promise for the completion of which ever callback is executed.

  • Attempt to run this Task to completion, but stop if the passed Timer, or one constructed from a passed time in milliseconds, elapses first.

    If this Task and the duration happen to have the same duration, timeout will favor this Task over the timeout.

    Parameters

    • timerOrMs: number | Timer

      A Timer or a number of milliseconds to wait for this task before timing out.

    Returns Task<T, E | Timeout>

    A Task which has the resolution value of this or a Timeout if the timer elapsed.

  • Get the underlying Promise. Useful when you need to work with an API which requires a Promise, rather than a PromiseLike.

    Note that this maintains the invariants for a Task up till the point you call this function. That is, because the resulting promise was managed by a Task, it always resolves successfully to a Result. However, calling then then or catch methods on that Promise will produce a new Promise for which those guarantees do not hold.

    Important

    If the resulting Promise ever rejects, that is a BUG, and you should open an issue so we can fix it!

    Returns Promise<Result<T, E>>

  • Returns a string representation of an object.

    Returns string