The *Async naming convention
Every lazy and aggregate operator has a twin that works on FxAsyncIterable — same behavior, async-friendly callback.
Lecture
Because Dart has no untyped currying, FxDart can't overload one
map to work on both Iterable and
FxAsyncIterable in data-first position — the parameter types
would collide. So every lazy and aggregate operator ships in two
top-level forms: the plain one for Iterable, and an
*Async twin for FxAsyncIterable whose callback
returns FutureOr<R> instead of R. You've
already met a few: map/mapAsync,
filter/filterAsync,
toList/toListAsync,
reduce/reduceAsync, fold/foldAsync,
each/eachAsync, find/findAsync —
the pattern holds for essentially every function in the library.
The chain forms don't need this split. Once you call
.toAsync() (or start from fxAsync/fxStream),
every subsequent method on that FxAsync chain keeps its
plain name — .map(...), not
.mapAsync(...) — because the receiver's type already tells
Dart which overload to use. The suffix only exists at the top level,
where data-first calls need it to disambiguate.
Demo 1 · A few twins, side by side
Data-first calls always need the Async suffix once you're
working with an FxAsyncIterable:
Demo 2 · Data-first vs. chain form
The chain form reads the same as its sync counterpart — no suffixes —
once .toAsync() has switched the receiver's type:
Try it yourself
Exercise: use the data-first *Async twin of map
to uppercase every name in this async pipeline.
toAsync — where async pipelines start ·
Stream bridges — fromStream, fxStream, toStream ·
concurrent — parallel evaluation ·
map — the sync original