Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fifty-tips-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'neverthrow': patch
---

JS Docs have been added for all exported API members.
163 changes: 163 additions & 0 deletions src/result-async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,48 @@ import {
InferOkTypes,
} from './_internals/utils'

/**
* A `ResultAsync` is a Promise of a {@link Result}.
*
* It is thenable and behaves like a native `Promise<Result>`, while also
* exposing the same methods that `Result` provides without needing to `await`
* or `.then` the promise first.
*/
export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
private _promise: Promise<Result<T, E>>

constructor(res: Promise<Result<T, E>>) {
this._promise = res
}

/**
* Same as {@link ResultAsync.fromPromise} except that it does not handle
* promise rejection.
*
* Ensure you know what you're doing: a thrown exception within this promise
* will cause `ResultAsync` to reject, instead of resolve to a `Result`.
*
* @param promise a promise that is known not to reject
*/
static fromSafePromise<T, E = never>(promise: PromiseLike<T>): ResultAsync<T, E>
static fromSafePromise<T, E = never>(promise: Promise<T>): ResultAsync<T, E> {
const newPromise = promise.then((value: T) => new Ok<T, E>(value))

return new ResultAsync(newPromise)
}

/**
* Transforms a `PromiseLike<T>` (that may reject) into a `ResultAsync<T, E>`.
*
* The second argument handles the rejection case and maps the error from
* `unknown` into some type `E`.
*
* If you know the promise will not reject, use {@link ResultAsync.fromSafePromise}
* instead to avoid a redundant error handler.
*
* @param promise the promise to wrap
* @param errorFn maps a rejection reason to an error of type `E`
*/
static fromPromise<T, E>(promise: PromiseLike<T>, errorFn: (e: unknown) => E): ResultAsync<T, E>
static fromPromise<T, E>(promise: Promise<T>, errorFn: (e: unknown) => E): ResultAsync<T, E> {
const newPromise = promise
Expand All @@ -42,6 +70,17 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
return new ResultAsync(newPromise)
}

/**
* Similar to {@link Result.fromThrowable}, but for functions that return a
* `Promise`.
*
* Safer than wrapping a function call with {@link ResultAsync.fromPromise},
* because not all promise-returning functions are `async` and may throw
* synchronously rather than returning a rejected promise.
*
* @param fn async (or promise-returning) function to wrap
* @param errorFn when an error is thrown or the promise rejects, maps it to `E`
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
static fromThrowable<A extends readonly any[], R, E>(
fn: (...args: A) => Promise<R>,
Expand All @@ -60,6 +99,17 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
}
}

/**
* Combines a list of `ResultAsync`s into a single `ResultAsync`.
*
* If all results are `Ok`, returns an `Ok` containing a list of all values.
* If any result is an `Err`, short-circuits and returns the first `Err`.
*
* Works on both homogeneous and heterogeneous lists (but not mixed with
* `Result`). Conceptually similar to `Promise.all`.
*
* @param asyncResultList list of `ResultAsync`s to combine
*/
static combine<
T extends readonly [ResultAsync<unknown, unknown>, ...ResultAsync<unknown, unknown>[]]
>(asyncResultList: T): CombineResultAsyncs<T>
Expand All @@ -72,6 +122,15 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
return (combineResultAsyncList(asyncResultList) as unknown) as CombineResultAsyncs<T>
}

/**
* Like {@link ResultAsync.combine} but without short-circuiting.
*
* Instead of returning only the first error, returns a list of all error
* values from failed results. There is no guarantee about the length of the
* error list if only some results fail.
*
* @param asyncResultList list of `ResultAsync`s to combine
*/
static combineWithAllErrors<
T extends readonly [ResultAsync<unknown, unknown>, ...ResultAsync<unknown, unknown>[]]
>(asyncResultList: T): CombineResultsWithAllErrorsArrayAsync<T>
Expand All @@ -86,6 +145,15 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
) as CombineResultsWithAllErrorsArrayAsync<T>
}

/**
* Maps a `ResultAsync<T, E>` to `ResultAsync<U, E>` by applying a function to
* a contained `Ok` value, leaving an `Err` value untouched.
*
* The applied function can be synchronous or asynchronous (returning a
* `Promise<U>`) with no impact to the return type.
*
* @param f The function to apply to an `Ok` value
*/
map<A>(f: (t: T) => A | Promise<A>): ResultAsync<A, E> {
return new ResultAsync(
this._promise.then(async (res: Result<T, E>) => {
Expand All @@ -98,6 +166,12 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
)
}

/**
* Similar to {@link ResultAsync.andTee} except that when the passed-in
* function returns an `Err`, that error is passed along.
*
* @param f The function to apply to the current value
*/
andThrough<F>(f: (t: T) => Result<unknown, F> | ResultAsync<unknown, F>): ResultAsync<T, E | F> {
return new ResultAsync(
this._promise.then(async (res: Result<T, E>) => {
Expand All @@ -114,6 +188,15 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
)
}

/**
* Lets the original `ResultAsync<T, E>` pass through regardless of the result
* of the passed-in function.
*
* Handy for side effects (such as logging) whose failure or success should
* not affect main logic.
*
* @param f The function to apply to the current value
*/
andTee(f: (t: T) => unknown): ResultAsync<T, E> {
return new ResultAsync(
this._promise.then(async (res: Result<T, E>) => {
Expand All @@ -130,6 +213,15 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
)
}

/**
* Like {@link ResultAsync.andTee} for the error track. Lets the original `Err`
* value pass through regardless of the result of the passed-in function.
*
* Handy for side effects (such as logging) whose failure or success should
* not affect main logic.
*
* @param f The function to apply to the current `Err` value
*/
orTee(f: (t: E) => unknown): ResultAsync<T, E> {
return new ResultAsync(
this._promise.then(async (res: Result<T, E>) => {
Expand All @@ -146,6 +238,15 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
)
}

/**
* Maps a `ResultAsync<T, E>` to `ResultAsync<T, F>` by applying a function to
* a contained `Err` value, leaving an `Ok` value untouched.
*
* The applied function can be synchronous or asynchronous (returning a
* `Promise<F>`) with no impact to the return type.
*
* @param f a function to apply to the error `Err` value
*/
mapErr<U>(f: (e: E) => U | Promise<U>): ResultAsync<T, U> {
return new ResultAsync(
this._promise.then(async (res: Result<T, E>) => {
Expand All @@ -158,6 +259,16 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
)
}

/**
* Same idea as {@link ResultAsync.map}, except the applied function must
* return a `Result` or `ResultAsync`.
*
* Always returns a `ResultAsync` regardless of the applied function's return
* type. Useful for subsequent computations that might fail, and for flattening
* nested `ResultAsync`s.
*
* @param f The function to apply to the current value
*/
andThen<R extends Result<unknown, unknown>>(
f: (t: T) => R,
): ResultAsync<InferOkTypes<R>, InferErrTypes<R> | E>
Expand All @@ -179,6 +290,13 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
)
}

/**
* Takes an `Err` value and maps it to a `Result` or `ResultAsync`. Useful for
* error recovery.
*
* @param f A function to apply to an `Err` value, leaving `Ok` values
* untouched.
*/
orElse<R extends Result<unknown, unknown>>(
f: (e: E) => R,
): ResultAsync<InferOkTypes<R> | T, InferErrTypes<R>>
Expand All @@ -199,10 +317,27 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
)
}

/**
* Given 2 functions (one for the `Ok` variant and one for the `Err` variant)
* execute the function that matches the `ResultAsync` variant.
*
* Unlike `Result.match`, always returns a `Promise` because of the
* asynchronous nature of `ResultAsync`.
*
* @param ok callback for the `Ok` variant
* @param _err callback for the `Err` variant
*/
match<A, B = A>(ok: (t: T) => A, _err: (e: E) => B): Promise<A | B> {
return this._promise.then((res) => res.match(ok, _err))
}

/**
* Unwrap the `Ok` value, or return the default if there is an `Err`.
*
* Works like `Result.unwrapOr` but returns a `Promise<T>` instead of `T`.
*
* @param t the default value to return if there is an `Err`
*/
unwrapOr<A>(t: A): Promise<T | A> {
return this._promise.then((res) => res.unwrapOr(t))
}
Expand Down Expand Up @@ -244,21 +379,49 @@ export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
}
}

/**
* Constructs an `Ok` variant of `ResultAsync`.
*
* @param value the success value to wrap
*/
export function okAsync<T, E = never>(value: T): ResultAsync<T, E>
export function okAsync<T extends void = void, E = never>(value: void): ResultAsync<void, E>
export function okAsync<T, E = never>(value: T): ResultAsync<T, E> {
return new ResultAsync(Promise.resolve(new Ok<T, E>(value)))
}

/**
* Constructs an `Err` variant of `ResultAsync`.
*
* @param err the error value to wrap
*/
export function errAsync<T = never, E = unknown>(err: E): ResultAsync<T, E>
export function errAsync<T = never, E extends void = void>(err: void): ResultAsync<T, void>
export function errAsync<T = never, E = unknown>(err: E): ResultAsync<T, E> {
return new ResultAsync(Promise.resolve(new Err<T, E>(err)))
}

/**
* Top-level export of {@link ResultAsync.fromPromise}.
*
* Transforms a `PromiseLike<T>` that may reject into a `ResultAsync<T, E>`.
*/
export const fromPromise = ResultAsync.fromPromise

/**
* Top-level export of {@link ResultAsync.fromSafePromise}.
*
* Like `fromPromise` but does not handle rejection — use only when you know
* the promise will not reject.
*/
export const fromSafePromise = ResultAsync.fromSafePromise

/**
* Top-level export of {@link ResultAsync.fromThrowable}.
*
* Wraps a promise-returning function so it returns `ResultAsync` instead of
* throwing or rejecting.
*/
export const fromAsyncThrowable = ResultAsync.fromThrowable

// Combines the array of async results into one result.
Expand Down
Loading