Poll a flaky API until first success
Requirement
An export job's status endpoint is deterministically flaky: the first
four polls answer pending, the fifth answers
ready. Poll it up to ten times, keep a log of every poll,
stop at the first success, and report which attempt won — plus how many
polls were actually made. No randomness: the failure count is fixed in
the code below, so both versions print the same thing every run.
The FxDart version writes the retry as data: range(1, 11)
is the poll schedule, map is the transport,
peek records the log, and dropWhile +
head is the success policy. Because the chain is lazy and
pulled one value at a time, head stops the polling — only
five requests are ever made.
Expected output
polling export-2026-07 (up to 10 attempts): poll 1: pending poll 2: pending poll 3: pending poll 4: pending poll 5: ready ready on attempt 5; polls actually made: 5
Side by side
Native Dart
FxDart
Why they differ
Honestly: the native for loop with a break is
short, and nobody would call it wrong. The difference is where the
pieces live. In the loop, the attempt budget, the logging, and the
success test are all tangled into control flow — change one and you
re-read the whole body. In the pipeline each concern is its own named
step, so swapping the policy (first success → third success, add a
transform, widen the budget) means editing one line. And the laziness
guarantee — no polls after the winner — is structural in FxDart, while
in the loop it depends on the break being in the right
place.
Benchmark
Async case: the headline scale is N = 100,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 Native wins
Peak memory FxDart wins
N = 100,000
Time Native 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.