futureBinding<L, R> static method

Future<Either<L, R>> futureBinding<L, R>(
  1. @monadComprehensions FutureOr<R> block(
    1. EitherEffect<L> effect
    )
)

Monad comprehension. Syntactic sugar do-notation. Although using flatMap openly often makes sense, many programmers prefer a syntax that mimics imperative statements (called do-notation in Haskell, perform-notation in OCaml, computation expressions in F#, and for comprehension in Scala). This is only syntactic sugar that disguises a monadic pipeline as a code block.

Calls the specified function block with EitherEffect as its parameter and returns its Either wrapped in a Future.

When inside a Either.futureBinding block, calling the EitherEffect.bind function will attempt to unwrap the Either and locally return its Right.value. If the Either is a Left, the binding block will terminate with that bind and return that failed-to-bind Left.

When inside a Either.futureBinding block, calling the BindFutureEitherEffectExtension.bindFuture function will attempt to will attempt to unwrap the Either inside the Future. and locally return its Right.value wrapped in a Future. If the Either is a Left, the binding block will terminate with that bind and return that failed-to-bind Left. If the Future completes with an error, it will not be handled.

You can also use BindEitherExtension.bind instead of EitherEffect.bind, BindEitherFutureExtension.bind instead of BindFutureEitherEffectExtension.bindFuture for more convenience.

Example

class ExampleError {}

Either<ExampleError, int> provideX() { ... }
Future<Either<ExampleError, int>> provideY() { ... }
Future<Either<ExampleError, int>> provideZ(int x, int y) { ... }

Future<Either<ExampleError, int>> result = Either.futureBinding<ExampleError, int>((e) async {
  int x = provideX().bind(e);                   // or use `e.bind(provideX())`.
  int y = await e.bindFuture(provideY());       // or use `await provideY().bind(e)`.
  int z = await provideZ(x, y).bind(e);         // or use `await e.bindFuture(provideZ(x, y))`.
  return z;
});

NOTE

/// This function can throw an error.
int canThrowAnError() { ... }
Future<int> canReturnAnErrorFuture() { ... }
Future<int> errorFuture = Future.error(Exception());

// DON'T
Future<Either<ExampleError, int>> result = Either.futureBinding<ExampleError, int>((e) async {
  int value1 = canThrowAnError();                // DON'T
  int value2 = await canReturnAnErrorFuture();   // DON'T
  int value3 = await errorFuture;                // DON'T
  return value1 + value2 + value3;
});

// DO
ExampleError toExampleError(Object e, StackTrace st) { ... }

Future<Either<ExampleError, int>> result = Either.futureBinding<ExampleError, int>((e) async {
  int value1 = Either<ExampleError, int>.catchError(
    toExampleError,
    canThrowAnError
  ).bind(e);

  int value2 = await Either.catchFutureError<ExampleError, int>(
    toExampleError,
    canReturnAnErrorFuture
  ).bind(e);

  int value3 = await Either.catchFutureError<ExampleError, int>(
    toExampleError,
    () => errorFuture
  ).bind(e);

  return value1 + value2 + value3;
});

Implementation

static Future<Either<L, R>> futureBinding<L, R>(
    @monadComprehensions FutureOr<R> Function(EitherEffect<L> effect) block) {
  final eitherEffect = _EitherEffectImpl<L>(_Token());

  return Future.sync(() => block(eitherEffect))
      .then((value) => Either<L, R>.right(value))
      .onError<ControlError<L>>(
        (e, s) => Either.left(e._value),
        test: (e) => identical(eitherEffect._token, e._token),
      );
}