Retry with growing backoff
Requirement
The rate service is unavailable exactly twice, then serves. Retry with growing backoff — wait 40 ms after the first failure, 80 ms after the second — with a budget of three attempts. Print the payload, the attempt count, and the recorded backoff sequence (recorded when each wait is chosen, so the output is deterministic). Both versions must print the lines shown under Expected output.
Expected output
rates: EUR 0.85, GBP 0.74, JPY 148.20 attempts: 3 backoff: 40ms, 80ms
Side by side
RxDart
FxDart
Why they differ
Backoff is where the one-call symmetry of plain retry breaks.
RxDart's retryWhen is a meta-stream protocol: for every
error, your factory must return a notifier stream — emit a
value to trigger the retry, emit an error to give up. So "wait 40 ms,
then 80 ms" becomes mapping each failure to an
Rx.timer stream, and because the factory only sees one
error at a time, both the failure count and the attempt budget live
in mutable variables outside the operator. It works, and it
is maximally general — but you are hand-assembling a retry loop out
of streams.
FxDart treats backoff as what it is: a number that depends on how
many times you have failed. The delay hook on
retry receives the failure count (1, 2, …)
and returns a Duration — the whole policy is one
expression, and the budget is the same attempts
argument as before. Nothing about waiting between attempts requires
a stream, and the pull side never pretends it does.
Verdict FxDart, on concept count: one hook versus a notifier-stream factory, an external counter, and a timer stream per failure.
Benchmark
Async case: the headline scale is N = 10,000, not 1,000,000. Every element costs an event-loop turn on both sides, so a million real awaits would measure Dart's event loop for minutes — not the pipeline. Delays are zero-length and the example's concurrency limit is kept; what the bars compare is the pipeline machinery.
N = 100
Time Tie
Peak memory Tie
N = 10,000
Time FxDart wins
Peak memory FxDart wins
Bars are medians of repeated timed iterations in fresh processes per side (small N is batched for timer resolution). Sides within 5% of each other — or within 0.6 ms, a difference no person can perceive — count as a tie; close relative races are re-measured up to 5 runs. In an app, anything under a few milliseconds is invisible to the user regardless of which bar is shorter. Memory is peak process RSS. The Dart VM and the dataset are identical on both sides, so the difference between the two bars is what the pipeline itself holds onto.