UNPKG

true-myth

Version:

A library for safe functional programming in JavaScript, with first-class support for TypeScript

1,338 lines (1,090 loc) 69.1 kB
/** {@include doc/task.md} @module */ import Maybe from './maybe.js'; import Result from './result.js'; import Unit from './unit.js'; import * as Delay from './task/delay.js'; export { Delay }; /** Internal implementation details for {@linkcode Task}. */ declare class TaskImpl<T, E> implements PromiseLike<Result<T, E>> { #private; constructor(executor: (resolve: (value: T) => void, reject: (reason: E) => void) => void); then<A, B>(onSuccess?: (result: Result<T, E>) => A | PromiseLike<A>, onRejected?: (reason: unknown) => B | PromiseLike<B>): PromiseLike<A | B>; toString(): string; /** Produce a `Task<T, E>` from a promise of a {@linkcode Result Result<T, E>}. > [!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 > with `Promise<Result<T, E>>` you have created yourself (including via a > `Task`, of course). > > For any other `Promise<Result<T, E>>`, you should first attach a `catch` > handler which will also produce a `Result<T, E>`. > > If you call this with an unmanaged `Promise<Result<T, E>>`, that is, one > that has *not* correctly set up a `catch` handler, the rejection will > throw an {@linkcode UnsafePromise} error that will ***not*** be catchable > by awaiting the `Task` or its original `Promise`. This can cause test > instability and unpredictable behavior in your application. @param promise The promise from which to create the `Task`. @group Constructors @deprecated Use the module-scoped {@linkcode fromUnsafePromise} instead. */ static fromUnsafePromise<T, E>(promise: Promise<Result<T, E>>): Task<T, E>; /** Produce a `Task<T, unknown>` from a promise. To handle the error case and produce a concrete `Task<T, E>` instead, use the overload which accepts an `onRejection` handler instead. @param promise The promise from which to create the `Task`. @group Constructors @deprecated This will be removed at 9.0. Switch to the module-scoped function {@linkcode fromPromise}. */ static try<T>(promise: Promise<T>): Task<T, unknown>; /** Produce a {@linkcode Task Task<T, E>} from a `Promise<T>` and use a fallback value if the task rejects, ignoring the rejection reason. Notes: - To leave any error as `unknown`, use the overload which accepts only the promise. - To handle the rejection reason rather than ignoring it, use the overload which accepts a function. @param promise The promise from which to create the `Task`. @param rejectionValue A function to transform an unknown rejection reason into a known `E`. @group Constructors @deprecated This will be removed at 9.0. Switch to the module-level function {@linkcode safelyTryOr}, which accepts a callback instead. */ static tryOr<T, E>(promise: Promise<T>, rejectionValue: E): Task<T, E>; /** Produce a `Task<T, E>` from a `Promise<T>` and a function to transform an unknown error to `E`. To leave any error as `unknown`, use the overload which accepts only the promise. @param promise The promise from which to create the `Task`. @param onRejection A function to transform an unknown rejection reason into a known `E`. @group Constructors @deprecated This will be removed at 9.0. Switch to the module-level function {@linkcode safelyTryOrElse}, which accepts a callback instead. */ static tryOrElse<T, E>(promise: Promise<T>, onRejection: (reason: unknown) => E): Task<T, E>; /** Construct a `Task` which is already resolved. Useful when you have a value already, but need it to be available in an API which expects a `Task`. @group Constructors */ static resolve<T extends Unit, E = never>(): Task<Unit, E>; /** Construct a `Task` which is already resolved. Useful when you have a value already, but need it to be available in an API which expects a `Task`. @group Constructors */ static resolve<T, E = never>(value: T): Task<T, E>; /** Construct a `Task` which is already rejected. Useful when you have an error already, but need it to be available in an API which expects a `Task`. @group Constructors */ static reject<T = never, E extends {} = {}>(): Task<T, Unit>; /** Construct a `Task` which is already rejected. Useful when you have an error already, but need it to be available in an API which expects a `Task`. @group Constructors */ static reject<T = never, E = unknown>(reason: E): Task<T, E>; /** Build a {@linkcode Task Task<T, E>} from a {@linkcode Result Result<T, E>}. @deprecated Use the module-scoped {@linkcode fromResult} instead. */ static fromResult<T, E>(result: Result<T, E>): Task<T, E>; /** Create a pending `Task` and supply `resolveWith` and `rejectWith` helpers, similar to the [`Promise.withResolvers`][pwr] static method, but producing a `Task` with the usual safety guarantees. [pwr]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/withResolvers ## Examples ### Resolution ```ts let { task, resolveWith, rejectWith } = Task.withResolvers<string, Error>(); resolveWith("Hello!"); let result = await task.map((s) => s.length); let length = result.unwrapOr(0); console.log(length); // 5 ``` ### Rejection ```ts let { task, resolveWith, rejectWith } = Task.withResolvers<string, Error>(); rejectWith(new Error("oh teh noes!")); let result = await task.mapRejection((s) => s.length); let errLength = result.isErr ? result.error : 0; console.log(errLength); // 5 ``` @group Constructors */ static withResolvers<T, E>(): WithResolvers<T, E>; get state(): State; get isPending(): boolean; get isResolved(): boolean; get isRejected(): boolean; /** The value of a resolved `Task`. > [!WARNING] > It is an error to access this property on a `Task` which is `Pending` or > `Rejected`. */ get value(): T; /** The cause of a rejection. > [!WARNING] > It is an error to access this property on a `Task` which is `Pending` or > `Resolved`. */ get reason(): E; /** Map over a {@linkcode 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 {@linkcode Rejected} without modification. `map` works a lot like [`Array.prototype.map`][array-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. [array-map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map 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`. ## Examples ```ts 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!") ``` @template T The type of the resolved value. @template U The type of the resolved value of the returned `Task`. @param mapFn The function to apply the value to when the `Task` finishes if it is `Resolved`. */ map<U>(mapFn: (t: T) => U): Task<U, E>; /** Map over a {@linkcode Task}, exactly as in {@linkcode 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. ## Examples ```ts 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") ``` @template T The type of the value produced if the `Task` resolves. @template E The type of the rejection reason if the `Task` rejects. @template F The type of the rejection for the new `Task`, returned by the `mapFn`. @param mapFn The function to apply to the rejection reason if the `Task` is rejected. */ mapRejected<F>(mapFn: (e: E) => F): Task<T, F>; /** You can think of this like a short-circuiting logical "and" operation on a {@linkcode 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 {@linkcode Rejected}. Notice that, unlike in {@linkcode map Task.prototype.map}, the original `task` resolution value is not involved in constructing the new `Task`. ## Examples ```ts 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")'); ``` @template 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`. @param other The `Task` instance to return if `this` is `Rejected`. */ and<U, F = E>(other: Task<U, F>): Task<U, E | F>; /** Apply a function to the resulting value if a {@linkcode Task} is {@linkcode Resolved}, producing a new `Task`; or if it is {@linkcode 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`][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 `Task`s. `Promise.prototype.then` also acts the same way {@linkcode map 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][bind]. [then]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then [bind]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind ## Examples ```ts 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) ``` @template U The type of the value produced by the new `Task` of the `Result` returned by the `thenFn`. @param thenFn The function to apply to the wrapped `T` if `maybe` is `Just`. */ andThen<U, F = E>(thenFn: (t: T) => Task<U, F>): Task<U, E | F>; /** Provide a fallback for a given {@linkcode Task}. Behaves like a logical `or`: if the `task` value is {@linkcode 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 {@linkcode Resolved} variant, by supplying a default value for the case that you currently have an {@linkcode Rejected}. ```ts 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:") ``` @template F The type wrapped in the `Rejected` case of `other`. @param other The `Result` to use if `this` is `Rejected`. @returns `this` if it is `Resolved`, otherwise `other`. */ or<F, U = T>(other: Task<U, F>): Task<T | U, F>; /** Like {@linkcode or}, but using a function to construct the alternative {@linkcode 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 {@linkcode Rejected} to something usable as an {@linkcode Resolved}, or generate a new `Rejected` instance as appropriate. Useful for transforming failures to usable data, for trigger retries, etc. @param elseFn The function to apply to the `Rejection` reason if the `Task` rejects, to create a new `Task`. */ orElse<F, U = T>(elseFn: (reason: E) => Task<U, F>): Task<T | U, F>; /** Allows you to produce a new value by providing functions to operate against both the {@linkcode Resolved} and {@linkcode Rejected} states once the {@linkcode Task} resolves. (This is a workaround for JavaScript’s lack of native pattern-matching.) ## Example ```ts 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`: > > ```ts > 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! @param matcher A lightweight object defining what to do in the case of each variant. */ match<A>(matcher: Matcher<T, E, A>): Promise<A>; /** Attempt to run this {@linkcode Task} to completion, but stop if the passed {@linkcode 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. @param timerOrMs A {@linkcode Timer} or a number of milliseconds to wait for this task before timing out. @returns A `Task` which has the resolution value of `this` or a `Timeout` if the timer elapsed. */ timeout(timerOrMs: Timer | number): Task<T, E | Timeout>; /** 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](https://github.com/true-myth/true-myth/issues) so > we can fix it! */ toPromise(): Promise<Result<T, E>>; } /** An unknown {@linkcode Task}. This is a private type utility; it is only exported for the sake of internal tests. @internal */ export declare type AnyTask = Task<unknown, unknown>; export declare type TaskTypesFor<A extends readonly AnyTask[]> = [ { -readonly [P in keyof A]: ResolvesTo<A[P]>; }, { -readonly [P in keyof A]: RejectsWith<A[P]>; } ]; /** The resolution type for a given {@linkcode Task}. @internal */ export declare type ResolvesTo<T extends AnyTask> = T extends Task<infer Value, infer _> ? Value : never; /** The rejection type for a given {@linkcode Task} @internal */ export declare type RejectsWith<T extends AnyTask> = T extends Task<infer _, infer Rejection> ? Rejection : never; /** Create a {@linkcode Task} which will resolve to {@linkcode Unit} after a set interval. (Safely wraps [`setTimeout`][setTimeout].) [setTimeout]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout This can be combined with the {@linkcode Task.timeout} instance method. @param ms The number of milliseconds to wait before resolving the `Task`. @returns a Task which resolves to the passed-in number of milliseconds. */ export declare function timer(ms: number): Timer; /** A type utility for mapping an input array of tasks into the appropriate output for `all`. @internal */ export declare type All<A extends readonly AnyTask[]> = Task<[ ...TaskTypesFor<A>[0] ], TaskTypesFor<A>[1][number]>; /** 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") ``` @param tasks The list of tasks to wait on. @template A The type of the array or tuple of tasks. */ export declare function all(tasks: []): Task<[], never>; export declare function all<A extends readonly AnyTask[]>(tasks: readonly [...A]): All<[...A]>; /** @internal */ export declare type Settled<A extends readonly AnyTask[]> = { -readonly [P in keyof A]: Result<ResolvesTo<A[P]>, RejectsWith<A[P]>>; }; /** Given an array of tasks, return a new {@linkcode Task} 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 }), ``` @param tasks The tasks to wait on settling. @template A The type of the array or tuple of tasks. */ export declare function allSettled<A extends readonly AnyTask[]>(tasks: A): Task<Settled<A>, never>; /** Given an array of tasks, return a new {@linkcode Task} 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.race`: 10ms,20ms,30ms) ``` The order in the resulting `AggregateRejection` is guaranteed to be stable and to match the order of the tasks passed in. @param tasks The set of tasks to check for any resolution. @returns A Task which is either {@linkcode Resolved} with the value of the first task to resolve, or {@linkcode Rejected} with the rejection reasons for all the tasks passed in in an {@linkcode AggregateRejection}. Note that the order of the rejection reasons is not guaranteed. @template A The type of the array or tuple of tasks. */ export declare function any(tasks: []): Task<never, AggregateRejection<[]>>; export declare function any<A extends readonly AnyTask[]>(tasks: readonly [...A]): Task<TaskTypesFor<A>[0][number], AggregateRejection<[...TaskTypesFor<A>[1]]>>; /** Given an array of tasks, produce a new {@linkcode Task} 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!") ``` @param tasks The tasks to race against each other. @template A The type of the array or tuple of tasks. */ export declare function race(tasks: []): Task<never, never>; export declare function race<A extends readonly AnyTask[]>(tasks: A): Task<TaskTypesFor<A>[0][number], TaskTypesFor<A>[1][number]>; /** An error type produced when {@linkcode any} produces any rejections. All rejections are aggregated into this type. > [!NOTE] > This error type is not allowed to be subclassed. @template E The type of the rejection reasons. */ export declare class AggregateRejection<E extends unknown[]> extends Error { readonly errors: E; readonly name = "AggregateRejection"; constructor(errors: E); toString(): string; } /** A {@linkcode Task Task<T, E>} that has not yet resolved. @template T The type of the value when the `Task` resolves successfully. @template E The type of the rejection reason when the `Task` rejects. @group Task Variants */ export interface Pending<T, E> extends Omit<TaskImpl<T, E>, 'value' | 'reason'> { get isPending(): true; get isResolved(): false; get isRejected(): false; get state(): typeof State.Pending; } /** A {@linkcode Task Task<T, E>} that has resolved. Its `value` is of type `T`. @template T The type of the value when the `Task` resolves successfully. @template E The type of the rejection reason when the `Task` rejects. @group Task Variants */ export interface Resolved<T, E> extends Omit<TaskImpl<T, E>, 'reason'> { get isPending(): false; get isResolved(): true; get isRejected(): false; get state(): typeof State.Resolved; get value(): T; } /** A {@linkcode Task Task<T, E>} that has rejected. Its `reason` is of type `E`. @template T The type of the value when the `Task` resolves successfully. @template E The type of the rejection reason when the `Task` rejects. @group Task Variants */ export interface Rejected<T, E> extends Omit<TaskImpl<T, E>, 'value'> { get isPending(): false; get isResolved(): false; get isRejected(): true; get state(): typeof State.Rejected; get reason(): E; } export declare const State: { readonly Pending: "Pending"; readonly Resolved: "Resolved"; readonly Rejected: "Rejected"; }; declare type State = (typeof State)[keyof typeof State]; /** Type returned by calling {@linkcode Task.withResolvers} */ export declare type WithResolvers<T, E> = { task: Task<T, E>; resolve: (value: T) => void; reject: (reason: E) => void; }; /** A lightweight object defining how to handle each outcome state of a {@linkcode Task}. */ export declare type Matcher<T, E, A> = { Resolved: (value: T) => A; Rejected: (reason: E) => A; }; /** The error thrown when an error is thrown in the executor passed to {@linkcode Task.constructor}. This error class exists so it is clear exactly what went wrong in that case. @group Errors */ export declare class TaskExecutorException extends Error { name: string; constructor(originalError: unknown); } /** An error thrown when the `Promise<Result<T, E>>` passed to {@link fromUnsafePromise} rejects. @group Errors */ export declare class UnsafePromise extends Error { readonly name = "TrueMyth.Task.UnsafePromise"; constructor(unhandledError: unknown); } export declare class InvalidAccess extends Error { readonly name = "TrueMyth.Task.InvalidAccess"; constructor(field: 'value' | 'reason', state: State); } /** @inheritdoc Task.tryOr */ export declare const tryOr: typeof TaskImpl.tryOr; /** @inheritdoc Task.tryOrElse */ export declare const tryOrElse: typeof TaskImpl.tryOrElse; /** The public interface for the {@linkcode Task} class *as a value*: a constructor and the associated static properties. */ export interface TaskConstructor extends Omit<typeof TaskImpl, 'constructor'> { /** Construct a new `Task`, using callbacks to wrap APIs which do not natively provide a `Promise`. 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`, a `Task` always “succeeds” in producing a usable value, just like {@linkcode Result} for synchronous code. [promise]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise For constructing a `Task` from an existing `Promise`, see: - {@linkcode fromPromise} - {@linkcode safelyTry} - {@linkcode safelyTryOr} - {@linkcode safelyTryOrElse} For constructing a `Task` immediately resolved or rejected with given values, see {@linkcode Task.resolve} and {@linkcode Task.reject} respectively. @param 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. */ new <T, E>(executor: (resolve: (value: T) => void, reject: (reason: E) => void) => void): Task<T, E>; } /** A `Task` is a type safe asynchronous computation. You can think of a `Task<T, E>` as being basically a `Promise<Result<T, E>>`, because it *is* a `Promise<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 {@linkcode Result}, it will *never* throw an error if used in strict TypeScript. 2. Unlike `Promise`, `Task` robustly distinguishes between `map` and `andThen` operations. `Task` also implements JavaScript’s `PromiseLike` interface, so you can `await` it; when a `Task<T, E>` is awaited, it produces a {@linkcode result Result<T, E>}. @class */ export declare const Task: TaskConstructor; /** A `Task` is a type safe asynchronous computation. You can think of a `Task<T, E>` as being basically a `Promise<Result<T, E>>`, because it *is* a `Promise<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 {@linkcode Result}, it will *never* throw an error if used in strict TypeScript. 2. Unlike `Promise`, `Task` robustly distinguishes between `map` and `andThen` operations. `Task` also implements JavaScript’s `PromiseLike` interface, so you can `await` it; when a `Task<T, E>` is awaited, it produces a {@linkcode result Result<T, E>}. @class @template T The type of the value when the `Task` resolves successfully. @template E The type of the rejection reason when the `Task` rejects. */ export declare type Task<T, E> = Pending<T, E> | Resolved<T, E> | Rejected<T, E>; export default Task; declare const PhantomData: unique symbol; /** @internal */ export declare class Phantom<T extends PropertyKey> { private readonly [PhantomData]; } /** A {@linkcode Task} specialized for use with {@linkcode timeout} 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*. */ export declare type Timer = Task<number, never> & Phantom<'Timer'>; /** An `Error` type representing a timeout, as when a {@linkcode Timer} elapses. */ declare class Timeout extends Error { #private; readonly ms: number; get duration(): number; constructor(ms: number); } export type { Timeout }; /** Standalone function version of {@linkcode Task.resolve} */ export declare const resolve: typeof TaskImpl.resolve; /** Standalone function version of {@linkcode Task.reject} */ export declare const reject: typeof TaskImpl.reject; /** Standalone function version of {@linkcode Task.withResolvers} */ export declare const withResolvers: typeof TaskImpl.withResolvers; /** Produce a {@linkcode Task Task<T, unknown>} 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 an `onRejection` 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 {@linkcode safelyTry}, {@linkcode safelyTryOr}, or > {@linkcode safelyTryOrElse} for alternatives which accept a callback for > constructing a promise and can therefore handle errors thrown in the call. @param promise The promise from which to create the `Task`. @template T The type the `Promise` would resolve to, and thus that the `Task` will also resolve to if the `Promise` resolves. @group Constructors */ export declare function fromPromise<T>(promise: Promise<T>): Task<T, unknown>; /** Produce a {@linkcode Task Task<T, E>} 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 an `onRejection` 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 {@linkcode safelyTry}, {@linkcode safelyTryOr}, or > {@linkcode safelyTryOrElse} for alternatives which accept a callback for > constructing a promise and can therefore handle errors thrown in the call. @param promise The promise from which to create the `Task`. @param onRejection Transform errors from `unknown` to a known error type. @template T The type the `Promise` would resolve to, and thus that the `Task` will also resolve to if the `Promise` resolves. @template E The type of a rejected `Task` if the promise rejects. @group Constructors */ export declare function fromPromise<T, E>(promise: Promise<T>, onRejection: (reason: unknown) => E): Task<T, E>; /** Build a {@linkcode Task Task<T, E>} from a {@linkcode Result Result<T, E>}. > [!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 {@linkcode tryOr} and {@linkcode tryOrElse} as well as the corresponding > `Result.tryOr` and `Result.tryOrElse` methods for synchronous functions. ## Examples Given an `Ok`, `fromResult` will produces a {@linkcode Resolved} 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 {@linkcode Rejected} 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!")); ``` */ export declare function fromResult<T, E>(result: Result<T, E>): Task<T, E>; /** Produce a `Task<T, E>` from a promise of a {@linkcode Result Result<T, E>}. > [!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 > with `Promise<Result<T, E>>` you have created yourself (including via a > `Task`, of course). > > For any other `Promise<Result<T, E>>`, you should first attach a `catch` > handler which will also produce a `Result<T, E>`. > > If you call this with an unmanaged `Promise<Result<T, E>>`, that is, one > that has *not* correctly set up a `catch` handler, the rejection will > throw an {@linkcode UnsafePromise} error that will ***not*** be catchable > by awaiting the `Task` or its original `Promise`. This can cause test > instability and unpredictable behavior in your application. @param promise The promise from which to create the `Task`. @group Constructors */ export declare function fromUnsafePromise<T, E>(promise: Promise<Result<T, E>>): Task<T, E>; /** Given a function which takes no arguments and returns a `Promise`, return a {@linkcode Task Task<T, unknown>} for the result of invoking that function. This safely handles functions which fail synchronously or asynchronously, so unlike {@linkcode Task.try} is safe to use with values which may throw errors _before_ producing a `Promise`. ## Examples ```ts import { safelyTry } from 'true-myth/task'; function throws(): Promise<T> { 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!) ``` @param 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 invoking `fn`. */ export declare function safelyTry<T>(fn: () => Promise<T>): Task<T, unknown>; /** Given a function which takes no arguments and returns a `Promise` and a value of type `E` to use as the rejection if the `Promise` rejects, return a {@linkcode Task Task<T, E>} for the result of invoking that function. This safely handles functions which fail synchronously or asynchronously, so unlike {@linkcode fromPromise} is safe to use with values which may throw errors _before_ producing a `Promise`. ## Examples ```ts import { safelyTryOr } from 'true-myth/task'; function throws(): Promise<number> { throw new Error("Uh oh!"); } // Note: passing the function by name, *not* calling it. let theTask = safelyTryOr("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 { safelyTryOr } from 'true-myth/task'; function throws(): Promise<number> { throw new Error("Uh oh!"); } // Note: passing the function by name, *not* calling it. let withFallback = safelyTryOr<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 resulting `Task`, or else it will always be `unknown`. @param rejection The value to use if the `Promise` rejects. @param 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 invoking `fn`. */ export declare function safelyTryOr<T, E>(rejection: E, fn: () => Promise<T>): Task<T, E>; export declare function safelyTryOr<T, E>(rejection: E): (fn: () => Promise<T>) => Task<T, E>; /** Given a function which takes no arguments and returns a `PromiseLike` and a function which accepts an `unknown` rejection reason and transforms it into a known rejection type `E`, return a {@linkcode Task Task<T, E>} for the result of invoking that function. This safely handles functions which fail synchronously or asynchronously, so unlike {@linkcode fromPromise} is safe to use with values which may throw errors _before_ producing a `Promise`. ## Examples ```ts import { safelyTryOrElse } from 'true-myth/task'; function throws(): Promise<number> { throw new Error("Uh oh!"); } // Note: passing the function by name, *not* calling it. let theTask = safelyTryOr( (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 { safelyTryOr } from 'true-myth/task'; function throws(): Promise<number> { throw new Error("Uh oh!"); } // Note: passing the function by name, *not* calling it. let withFallback = safelyTryOrElse<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 resulting `Task`, or else it will always be `unknown`. @param onError The function to use to transform the rejection reasons if the `PromiseLike` produced by `fn` rejects. @param 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 invoking `fn`. */ export declare function safelyTryOrElse<T, E>(onError: (reason: unknown) => E, fn: () => PromiseLike<T>): Task<T, E>; export declare function safelyTryOrElse<T, E>(onError: (reason: unknown) => E): (fn: () => PromiseLike<T>) => Task<T, E>; /** Given a function which returns a `Promise`, return a new function with the same parameters but which returns a {@linkcode Task} 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 a `Task` instead of a `Promise` and which does not throw an error for rejections, but instead produces a {@Rejected} variant of the `Task`. ```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); ``` @param fn A function to wrap so it never throws an error or produces a `Promise` rejection. */ export declare function safe<F extends (...params: never[]) => PromiseLike<unknown>, P extends Parameters<F>, R extends Awaited<ReturnType<F>>>(fn: F): (...params: P) => Task<R, unknown>; /** Given a function which returns a `Promise` and a function to transform thrown errors or `Promise` rejections resulting from calling that function, return a new function with the same parameters but which returns a {@linkcode Task}. 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 a `Task` instead of a `Promise` and which does not throw an error for rejections, but instead produces a {@Rejected} variant of the `Task`. ```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); ``` @param fn A function to wrap so it never throws an error or produces a `Promise` rejection. @param onError A function to use to transform the */ export declare function safe<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` of a nullable type, return a new function with the same parameters but which returns a {@linkcode Task} of a {@linkcode Maybe} 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` with `Maybe.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` and `Maybe.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` the `Task`’s resolution value into a `Maybe` at each call site. @param fn A function to wrap so it never throws an error or produces a `Promise` rejection. */ export declare function 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>; /** Given a function which returns a `Promise` and a function to transform thrown errors or `Promise` rejections resulting from calling that function, return a new function with the same parameters but which returns a {@linkcode Task}. 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` with `Maybe.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` and `Maybe.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` the `Task`’s resolution value into a `Maybe` at each call site. @param fn A function to wrap so it never throws an error or produces a `Promise` rejection. @param onError A function to use to transform the */ exp