Skip to content

avoid_either_of_future

v1.0.0 Warning fpdart

This rule flags a Future nested inside the synchronous Either or Option — either written as a type (Either<Failure, Future<League>>) or produced by mapping with an async function.

Either and Option are synchronous wrappers. Mapping one with a function that returns a Future does not make the pipeline async — it makes an Either<L, Future<R>>.

That type is a trap. The future is created and starts running immediately, but it sits inside the success channel, so the error channel no longer covers it. A rejection becomes an unhandled async error rather than a Left, and callers receive a Right holding a future that may already have failed. Every fold written downstream still reports success.

The pipeline has to enter the async world at that point, which is exactly what toTaskEither() is for.

See also: fpdart: TaskEither, From sync to async functional programming

Either<Failure, Future<League>> save(LeagueDraft draft) =>
validate(draft).map((valid) => api.saveLeague(valid));

Convert once, early, then keep chaining in the async world:

TaskEither<Failure, League> save(LeagueDraft draft) =>
validate(draft).toTaskEither().flatMap(
(valid) => TaskEither.tryCatch(
() => api.saveLeague(valid),
(e, s) => Failure.from(e),
),
);

chainEither is the counterpart when a synchronous validation step joins an already-async pipeline.

TaskEither, TaskOption and the other async wrappers are unaffected — a Future belongs inside them.

An Iterable.map returning futures is ordinary Dart and is never reported; only fpdart’s synchronous wrappers are.

This rule is about a Future nested inside Either. The reverse nesting, Future<Either<L, R>>, is a different matter — it is correct, merely not idiomatic — and is covered by avoid_future_of_either.

This rule is in the recommended preset, so it is on with preset: recommended or preset: opinionated. Add it to preset: core with avoid_either_of_future: true.

To turn it off:

many_lints.yaml
rules:
avoid_either_of_future: false

To keep the rule on but skip certain paths, use per-rule exclude.