attempt & raiseLefts
Move a failure between the two channels a Dart Stream has: the error channel every listen(onError:) sees, and the value channel carrying Either.
Lecture
The events layer's own error tools —
onErrorReturn,
onErrorResume,
retryOn,
retryOnError — all speak untyped
Object, because that is what the error channel carries.
attempt is the bridge to the typed half of the library:
each data event becomes a Right, each error event a
Left built by onThrow. Once the failure is a
Left, the compiler knows its type and a
switch over the event cannot forget to handle it.
Convert at the source boundary and stay on the value channel
afterwards. A Dart error is not terminal, so the source keeps its
subscription and later events still arrive — the same reason
onErrorReturn substitutes per error rather than
rescuing once. The difference is the result type:
onErrorReturn keeps T by picking a
placeholder; attempt changes it to
Either<E, T> so the failure is named.
Place attempt after
retryOn / retryOnError / FxEvents.retry,
never before. Those operators watch the error channel, and there is
nothing left there to retry once the error has become a value.
raiseLefts is the other direction, on non-nullable
failures only, because Dart cannot throw null. It
unwraps each Right and puts each Left back
on the error channel, for a boundary that hands the stream to
Stream-based code expecting Dart errors. An
attempt / raiseLefts round trip keeps the
failure value and not its stack trace — Left does not
carry one.
Demo 1 · Errors become Left, the chain keeps running
Demo 2 · raiseLefts, the other direction
Try it yourself
Exercise: attempt after retry converts a retried
failure; attempt before retry leaves nothing on the
error channel to retry.
onErrorReturn / onErrorResume — recover on the error channel, untyped ·
mapEither — stay on the value channel; a raise becomes a Left ·
Either — the sealed result type these operators wrap