futureBinding<L, R> static method
- @monadComprehensions FutureOr<
R> block(- EitherEffect<
L> effect
- EitherEffect<
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
- Do NOT catch ControlError in
block
. - Do NOT throw any errors inside
block
. - When using BindFutureEitherEffectExtension.bindFuture, if the Future completes with an error, it will not be handled.
- Use Either.catchError, Either.catchFutureError or Either.catchStreamError to catch error,
then use EitherEffect.bind and BindFutureEitherEffectExtension.bindFuture to unwrap the
Either
.
/// 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),
);
}