Skip to main content

Future<Result<Ok, Error>>

A Future can contain a Result (e.g. to represent an asynchronous value that can fail). We provide some utility functions to deal with that case without having to unwrap the Future result.

note

You can still use all the regular Future methods. The following helpers simply removes the need to unwrap the contained result.

Methods

.mapOkToResult(f)

Future<Result<A, E>>.mapOkToResult<B, F>(
func: (value: A) => Result<B, F>,
propagateCancel?: boolean
): Future<Result<B, E | F>>

Takes a Future<Result<Ok, Error>> and a f function taking Ok and returning Result<ReturnValue, Error> and returns a new Future<Result<ReturnValue, Error>>

Examples
Future.value(Result.Ok(3)).mapOkToResult((ok) => {
return Result.Ok(ok * 2);
});
// Future<Result.Ok<6>>

Future.value(Result.Ok(3)).mapOkToResult((ok) =>
isEven(ok) ? Result.Ok(ok) : Result.Error("Odd number");
);
// Future<Result.Error<"Odd number">>

.mapErrorToResult(f)

Future<Result<A, E>>.mapErrorToResult<B, F>(
func: (value: E) => Result<B, F>,
propagateCancel?: boolean
): Future<Result<A | B, F>>

Takes a Future<Result<Ok, Error>> and a f function taking Error and returning Result<Ok, ReturnError> and returns a new Future<Result<Ok, ReturnError>>

Examples
Future.value(Result.Error(3)).mapErrorToResult((ok) => {
return Result.Ok(ok * 2);
});
// Future<Result.Ok<6>>

Future.value(Result.Error(3)).mapErrorToResult((ok) =>
isEven(ok) ? Result.Ok(ok) : Result.Error("Odd number");
);
// Future<Result.Error<"Odd number">>

.mapOk(f)

Future<Result<A, E>>.mapOk<B>(
func: (value: A) => B,
propagateCancel?: boolean
): Future<Result<B, E>>

Takes a Future<Result<Ok, Error>> and a f function taking Ok and returning ReturnValue and returns a new Future<Result<ReturnValue, Error>>

Examples
Future.value(Result.Ok(3)).mapOk((ok) => {
return ok * 2;
});
// Future<Result.Ok<6>>

Future.value(Result.Error("something")).mapOk((ok) => {
return ok * 2;
});
// Future<Result.Error<"something">>

.mapError(f)

Future<Result<A, E>>.mapError<F>(
func: (value: E) => F,
propagateCancel?: boolean
): Future<Result<A, F>>

Takes a Future<Result<Ok, Error>> and a f function taking Error and returning ReturnValue and returns a new Future<Result<Ok, ReturnValue>>

Examples
Future.value(Result.Error(3)).mapError((error) => {
return error * 2;
});
// Future<Result.Error<6>>

Future.value(Result.Ok("something")).mapError((ok) => {
return ok * 2;
});
// Future<Result.Ok<"something">>

.flatMapOk(f)

Future<Result<A, E>>.flatMapOk<B, F>(
func: (value: A) => Future<Result<B, F>>,
propagateCancel?: boolean
): Future<Result<B, E | F>>

Takes a Future<Result<Ok, Error>> and a f function taking Ok returning a Future<Result<ReturnValue, Error>>

Examples
Future.value(Result.Ok(3)).flatMapOk((ok) => Future.value(Result.Ok(ok * 2)));
// Future<Result.Ok<6>>

Future.value(Result.Ok(3)).flatMapOk((ok) =>
Future.value(Result.Error("Nope")),
);
// Future<Result.Error<"Nope">>

Future.value(Result.Error("Error")).flatMapOk((ok) =>
Future.value(Result.Ok(ok * 2)),
);
// Future<Result.Error<"Error">>

.flatMapError(f)

Future<Result<A, E>>.flatMapError<B, F>(
func: (value: E) => Future<Result<B, F>>,
propagateCancel?: boolean
): Future<Result<A | B, F>>

Takes a Future<Result<Ok, Error>> and a f function taking Error returning a Future<Result<Ok, ReturnValue>>

Examples
Future.value(Result.Ok(3)).flatMapError((error) =>
Future.value(Result.Ok(ok * 2)),
);
// Future<Result.Ok<3>>

Future.value(Result.Error("Error")).flatMapError((error) =>
Future.value(Result.Error("Nope")),
);
// Future<Result.Error<"Nope">>

Future.value(Result.Error("Error")).flatMapError((error) =>
Future.value(Result.Ok(1)),
);
// Future<Result.Ok<1>>

.tapOk(f)

Future<Result<A, E>>.tapOk(func: (value: A) => unknown): Future<Result<A, E>>

Runs f if value is Ok with the future value, and returns the original future. Useful for debugging.

Examples
future.tapOk(console.log);

.tapError(f)

Future<Result<A, E>>.tapError(func: (value: E) => unknown): Future<Result<A, E>>

Runs f if value is Error with the future value, and returns the original future. Useful for debugging.

Examples
future.tapError(console.log);

.resultToPromise()

Future<Result<A, E>>.resultToPromise(): Promise<A>

Takes a Future<Result<Ok, Error>> and returns a Promise<Ok>, rejecting the promise with Error in this state.

Examples
Future.value(Result.Ok(1)).resultToPromise();
// Promise<1>

Future.value(Result.Reject(1)).resultToPromise();
// Promise (rejected with 1)

Statics

Future.all(resultFutures)

You can combine the all helpers from Future and Result:

Examples
const futures = [
Future.value(Result.Ok(1)),
Future.value(Result.Ok(2)),
Future.value(Result.Ok(3)),
];

Future.all(futures).map(Result.all);
// Future<Result.Ok<[1, 2, 3]>>

Let's see the types at each step:

Examples
// Array<Future<Result<number, never>>>
// -> [Future<Result.Ok<1>>, Future<Result.Ok<2>>, Future<Result.Ok<3>>]
const input = [
Future.value(Result.Ok(1)),
Future.value(Result.Ok(2)),
Future.value(Result.Ok(3)),
];

// Future<Array<Result<number, never>>>
// -> Future<[Result.Ok<1>>, Result.Ok<2>>, Result.Ok<3>]>
const step1 = Future.all(input);

// Future<Result<Array<number>, never>>
// -> Future<[Result.Ok<[1, 2, 3]>>
const step2 = step1.map(Result.all);

Future.allFromDict(resultFutures)

Like as all, you can combine the allFromDict:

Examples
const futures = {
a: Future.value(Result.Ok(1)),
b: Future.value(Result.Ok(2)),
c: Future.value(Result.Ok(3)),
};

Future.allFromDict(futures).map(Result.allFromDict);
// Future<[Result.Ok<{a: 1, b: 2, c: 3}>>

Let's see the types at each step:

Examples
// Dict<Future<Result<number, never>>>
// -> {a: Future<Result.Ok<1>>, b: Future<Result.Ok<2>>, c: Future<Result.Ok<3>>—
const input = {
a: Future.value(Result.Ok(1)),
b: Future.value(Result.Ok(2)),
c: Future.value(Result.Ok(3)),
};

// Future<Dict<Result<number, never>>>
// -> Future<{a: Result.Ok<1>>, b: Result.Ok<2>>, c: Result.Ok<3>}>
const step1 = Future.all(input);

// Future<Result<Array<number>, never>>
// -> Future<[Result.Ok<{a: 1, b: 2, c: 3}>>
const step2 = step1.map(Result.all);

Future.retry(getFuture)

retry(getFuture: () => Future<Result<A, E>>, {max: number}): Future<Result<A, E>>

Runs the future getter, if the future resolves with a Result.Error, retries until hitting max attempts.

The function provides a 0-based attempt count to the function if you need to implement delay logic.

Examples
// retry immediately after failure
Future.retry(
(attempt) => {
return getUserById(userId);
},
{ max: 3 },
);
// Future<Result<...>>

// adding delay
Future.retry(
(attempt) => {
return Future.wait(attempt * 100).flatMap(() => getUserById(userId));
},
{ max: 10 },
);
// Future<Result<...>>

Cheatsheet

MethodInputFunction inputFunction outputReturned value
mapOkToResultFuture(Ok(x))xOk(y)Future(Ok(y))
mapOkToResultFuture(Ok(x))xError(f)Future(Error(f))
mapOkToResultFuture(Error(e))not providednot executedFuture(Error(e))
mapErrorToResultFuture(Error(e))eOk(y)Future(Ok(y))
mapErrorToResultFuture(Error(e))eError(f)Future(Error(f))
mapErrorToResultFuture(Ok(x))not providednot executedFuture(Ok(x))
mapOkFuture(Ok(x))xyFuture(Ok(y))
mapOkFuture(Error(e))not providednot executedFuture(Error(e))
mapErrorFuture(Ok(x))not providednot executedFuture(Ok(x))
mapErrorFuture(Error(e))efFuture(Error(f))
flatMapOkFuture(Ok(x))xFuture(Ok(y))Future(Ok(y))
flatMapOkFuture(Ok(x))xFuture(Error(f))Future(Error(f))
flatMapOkFuture(Error(e))not providednot executedFuture(Error(e))
flatMapErrorFuture(Ok(x))not providednot executedFuture(Ok(x))
flatMapErrorFuture(Error(e))eFuture(Ok(y))Future(Ok(y))
flatMapErrorFuture(Error(e))eFuture(Error(f))Future(Error(f))