Why is this `RuntimeException` in the `collect` bl...
# coroutines
l
Why is this
RuntimeException
in the
collect
block caught by the
try-catch
in the
flow
builder?
s
Because the code inside
collect
is actually run as part of the call to
emit
. Use the
catch
operator instead if you don’t want that behaviour (I think…)
d
In fact, flow is composition of wrapper functions: invocation of emit is invocation of collect function
Copy code
fun interface Collector: (Int) -> Unit

fun main() {
    val flow: (Collector) -> Unit = { emit: Collector ->
        emit(1)
    }
    val collect = Collector { next: Int ->
        error("oops")
    }
    flow(collect)
}
l
Thank you. Hmm, so another situation with coroutines/flows where you need to know the internals in order to understand the behaviour…
s
Or another reason to avoid using exceptions for error handling
l
@Sam And use the Result class instead?
s
Something like that, yeah. I prefer the
Either
type from #arrow which has a few advantages over
Result
.
But any sealed class will do 👍
c
This case isn’t so much “knowing the internals” as it is just understanding the use-case of Flows in general. There’s a lot of internal details with Flows related to deeper coroutine mechanisms (fusing, coroutine context, etc) that you really don’t need to know about. But knowing how data and exceptions flow through a chain of Flow operators is just part of its feature set, and the behavior is very much an intentional part of the Flow API. Each Flow operator basically wraps the entire chain before. So
.collect { }
basically wraps
flow { }
in such a way that emitted values flow down the chain, and exceptions flow back through all those layers in the opposite direction. It’s the same principle for why a function returns a value but exceptions are bubbled up the caller instead, because that’s the underlying mechanism and thought process of Flows: just a nested sequence of suspending function calls
e
@Casey Brooks if i understand u correctly, basically the order of functions goes like:
flow( emit( collect() ) )
and when collect throws an exception, it bubbles up to emit cuz emit was the caller/trigger of the collect call Just as any other exception bubbles up to its caller. Did i understand that correctly?
c
Yup, that’s the basic idea. You can even read the source of
flow { }
and
collect { }
to see that this is literally how the code flow is set up:
emit()
directly calls the lambda passed to
collect { }
. Things can get much more complicated with other operators, but this is the general principle behind it all